單調遞增最長子序列 (NYOJ 17) [動態規劃]

單調遞增最長子序列

時間限制:3000 ms  |  內存限制:65535 KB
難度:4
 
描述
求一個字符串的最長遞增子序列的長度
如:dabdbf最長遞增子序列就是abdf,長度爲4
 
輸入
第一行一個整數0<n<20,表示有n個字符串要處理
隨後的n行,每行有一個字符串,該字符串的長度不會超過10000
輸出
輸出字符串的最長遞增子序列的長度
樣例輸入
3
aaa
ababc
abklmncdefg
樣例輸出
    1
    3
    7
本題有多種解法:
一:最長公共子序列法
此方法用時較長,佔用內存也較大,且僅適用於有規律且有限長度的子序列中,代碼如下:
#include<stdio.h>
#include<string.h>
#define max(a,b) (a>b?a:b)
int dp[10010][30];
int main(void)
{
    char a[10010];
    char b[30]={'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};
    int n,len,i,j,k;
    int sum,max;    
    scanf("%d",&n);
    while(n--)
    {
        scanf("%s",a);
        len=strlen(a);        
        for(i=1;i<=len;i++)
        {
            for(j=1;j<=26;j++)
            {
                if(a[i-1]==b[j-1])
                {
                    dp[i][j]=dp[i-1][j-1]+1;
                }
                else
                {
                    dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
/*      for(i=0;i<len;i++)
        {
            for(j=0;j<26;j++)
            {
                printf("%d ",dp[i][j]);
            }
            printf("\n");
        }
*/        
        printf("%d\n",dp[len][26]);        
    }
    
    return 0;
}

二:O(n^2) 最大子段和法

此方法採用了標記數組,記錄每個當前狀態下的最優解。

本題中的核心代碼就是那兩個for循環來更新m和dp數組中的值,

其中第一個循環中,每次都須將m置零 ,
其中第二個for循環的意思就是更新m的值,
使m的值爲i位置字符之前的所有比i位置字符小的dp數組中所存值得最大值,直至第二個循環
結束,使i位置的dp數組爲m的值加上1,即可。

 

代碼如下:

#include<stdio.h>
#include<string.h>
char s[10010];
int dp[10010]={1};
int main(void)
{
    int n,len;
    int i,j,m;
    scanf("%d",&n);
    while(n--)
    {
        scanf("%s",s);
        len=strlen(s);
        for(i=1;i<len;i++)
        {
            m=0;
            for(j=i-1;j>=0;j--)
            {
                if(s[i]>s[j]&&m<dp[j])
                {
                    m=dp[j];
                }
            }
            dp[i]=m+1;
        }
        m=dp[0];
        for(i=0;i<len;i++)
        {
            if(m<dp[i])
            {
                m=dp[i];
            }
        }
        printf("%d\n",m);
    }
    return 0;
}

三:O(n^2)  此方法是方法二的優化,方法二中的標記數組過長,此方法採用標記字符串,所以標記數組的長度縮減到26即可,本代碼中用的長度爲30

本題中使用了標記字符串,先將待求字符串中的第一個字符賦到標記字符串中,
並使用k對標記字符串進行計數,

然後依次讀入待求字符串的字符,
並與標記字符串中的字符從後往前依次比較,

若標記字符串的字符比待求字符串的字符大,且此時處在標記字符串的起始位置
,則將此位置的待求字符賦到標記字符串中,

若標記字符串的字符比待求字符串的字符小,則將此處待求字符串的字符賦到標記字符串的下一個字符,如果此時已達到標記字符串的長度,則令k加1,並結束當前循環,重新開始比較,重新開始時,只有k增加了一,其餘各數據均不變。

代碼如下:

#include<stdio.h>
#include<string.h>
int main(void)
{
    char s[10010];
    char dp[35];
    int n,len,i,j,k;
    scanf("%d",&n);
    while(n--)
    {
        scanf("%s",s);
        len=strlen(s);
        dp[0]=s[0];
        k=1;
        for(i=0;i<len;i++)
        {
            for(j=k-1;j>=0;j--)
            {
                if(dp[j]>s[i]&&j==0)
                {
                    dp[0]=s[i];
                }
                if(dp[j]<s[i])
                {
                    dp[j+1]=s[i];
                    if(j==k-1)
                    {
                        k++;
                    }
                    break;
                }
            }
        }
        dp[k]='\0';
        printf("%d\n",k);        
    }
    
    return 0;
}

四:O(n*lg n)

此方法是對方法三的優化,

最長遞增子序列可以優化到 O(n*lg n)
通過優化方法二中的s[i]在dp[]什麼位置,使用二分查找。
if(a[i]>dp[k]) {k=k+1;dp[k]=s[i];}
if(a[i]<dp[1]) dp[1]=s[i];
其他情況找到 dp[j]<s[i]<dp[j+1]  dp[j+1]=s[i];

注:雖然說此方法是最優的解法,但是在oj上面提交的時候,並沒有方法三省時間,省內存。

#include<stdio.h>
#include<string.h>
#define N 10010
char s[N],dp[30];
int work(int n)
{
    int i,j,k,x,y,m;
    dp[0]=s[0];
    k=0;
    for(i=1;i<n;i++)
    {
        if(dp[k]<s[i])
        {
            k++;
            dp[k]=s[i];
            continue;
        }
        x=0;
        y=k;
        while(x<=y)
        {
            if(s[i]<dp[x])
            {
                j=x;
                break;
            }
            if(dp[y]<s[i])
            {
                j=y+1;
                break;
            }
            m=(x+y)/2;
            if(dp[m]<s[i])
            {
                x=m+1;
            }
            else if(s[i]<dp[m])
            {
                y=m-1;
            }
            else 
            {
                j=m;
                break;
            }
        }
        dp[j]=s[i];
    }
    return k+1;
}
int main(void)
{
    int n,i;
    scanf("%d",&n);    
    while(n--)
    {
        scanf("%s",s);
        printf("%d\n",work(strlen(s)));
    }
    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章