zoukankan      html  css  js  c++  java
  • 单调递增最长子序列 (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("
    ");
            }
    */        
            printf("%d
    ",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
    ",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]='';
            printf("%d
    ",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
    ",work(strlen(s)));
        }
        return 0;
    }
  • 相关阅读:
    mysql慢查询
    linux查找文件
    ss安装教程
    node_module删除
    api的错误代码设计
    es6的Promise
    vue后台项目
    vue的main.js
    vue的过渡效果
    【Linux网络基础】网络拓扑、OSI层次模型、TCP/IP协议簇
  • 原文地址:https://www.cnblogs.com/lbd_smile/p/4539044.html
Copyright © 2011-2022 走看看