zoukankan      html  css  js  c++  java
  • dp之最长递增、公共子序列总结

    1、最长递增子序列模板poj2533(时间复杂度O(n*n))

    #include<iostream>
    #include<stdio.h>
    #include<string.h>
    using namespace std;
    int dp[1005],a[1005];
    int main()
    {
        int n;
        while(scanf("%d",&n)>0)
        {
            for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
            for(int i=0;i<=n;i++)
            dp[i]=1;
            for(int i=1;i<=n;i++)
            {
                for(int j=1;j<i;j++)
                if(a[i]>a[j]&&dp[i]<dp[j]+1)
                dp[i]=dp[j]+1;
            }
            int maxx=0;
            for(int i=1;i<=n;i++)
            if(dp[i]>maxx)
            maxx=dp[i];
            printf("%d
    ",maxx);
        }
        return 0;
    }
    

     2、最长递增子序列模板poj3903(时间复杂度O(nlogn))

    最长递增子序列,Longest Increasing Subsequence 下面我们简记为 LIS。
    排序+LCS算法 以及 DP算法就忽略了,这两个太容易理解了。
    
    假设存在一个序列d[1..9] = 2 1 5 3 6 4 8 9 7,可以看出来它的LIS长度为5。
    下面一步一步试着找出它。
    我们定义一个序列B,然后令 i = 1 to 9 逐个考察这个序列。
    此外,我们用一个变量Len来记录现在最长算到多少了
    
    首先,把d[1]有序地放到B里,令B[1] = 2,就是说当只有1一个数字2的时候,长度为1的LIS的最小末尾是2。这时Len=1
    
    然后,把d[2]有序地放到B里,令B[1] = 1,就是说长度为1的LIS的最小末尾是1,d[1]=2已经没用了,很容易理解吧。这时Len=1
    
    接着,d[3] = 5,d[3]>B[1],所以令B[1+1]=B[2]=d[3]=5,就是说长度为2的LIS的最小末尾是5,很容易理解吧。这时候B[1..2] = 1, 5,Len=2
    
    再来,d[4] = 3,它正好加在1,5之间,放在1的位置显然不合适,因为1小于3,长度为1的LIS最小末尾应该是1,这样很容易推知,长度为2的LIS最小末尾是3,于是可以把5淘汰掉,这时候B[1..2] = 1, 3,Len = 2
    
    继续,d[5] = 6,它在3后面,因为B[2] = 3, 而6在3后面,于是很容易可以推知B[3] = 6, 这时B[1..3] = 1, 3, 6,还是很容易理解吧? Len = 3 了噢。
    
    第6个, d[6] = 4,你看它在3和6之间,于是我们就可以把6替换掉,得到B[3] = 4。B[1..3] = 1, 3, 4, Len继续等于3
    
    第7个, d[7] = 8,它很大,比4大,嗯。于是B[4] = 8。Len变成4了
    
    第8个, d[8] = 9,得到B[5] = 9,嗯。Len继续增大,到5了。
    
    最后一个, d[9] = 7,它在B[3] = 4和B[4] = 8之间,所以我们知道,最新的B[4] =7,B[1..5] = 1, 3, 4, 7, 9,Len = 5。
    
    于是我们知道了LIS的长度为5。
    
    !!!!! 注意。这个1,3,4,7,9不是LIS,它只是存储的对应长度LIS的最小末尾。有了这个末尾,我们就可以一个一个地插入数据。虽然最后一个d[9] = 7更新进去对于这组数据没有什么意义,但是如果后面再出现两个数字 8 和 9,那么就可以把8更新到d[5], 9更新到d[6],得出LIS的长度为6。
    
    然后应该发现一件事情了:在B中插入数据是有序的,而且是进行替换而不需要挪动——也就是说,我们可以使用二分查找,将每一个数字的插入时间优化到O(logN)~~~~~于是算法的时间复杂度就降低到了O(NlogN)~!
    #include<iostream>
    #include<stdio.h>
    #include<stdio.h>
    using namespace std;
    int dp[100005],s[100005];
    int main()
    {
        int n;
        while(scanf("%d",&n)>0)
        {
            for(int i=0;i<n;i++)
            scanf("%d",&s[i]);
            dp[0]=s[0];
            int len=1;
            for(int i=1;i<n;i++)
            {
                int left=0,right=len-1,mid;
                if(dp[len-1]<s[i])
                dp[len++]=s[i];
                else
                {
                    right=len-1;
                    while(left<=right)
                    {
                        mid=(left+right)/2;
                        if(dp[mid]<s[i])
                        left=mid+1;
                        else  right=mid-1;
                    }
                    dp[left]=s[i];
                }
            }
            printf("%d
    ",len);
        }
        return 0;
    } 
    

     3、最长公共子序列poj1458

    #include<iostream>
    #include<stdio.h>
    #include<string.h>
    using namespace std;
    int dp[1000][1000];
    char s[1000],t[1000];
    int main()
    {
        while(scanf("%s%s",s,t)>0)
        {
             int lens=strlen(s),lent=strlen(t);
             for(int i=0;i<1000;i++)
             dp[0][i]=dp[i][0]=0;
             for(int i=1;i<=lens;i++)
             {
                     for(int j=1;j<=lent;j++)
                     if(s[i-1]==t[j-1])
                     dp[i][j]=dp[i-1][j-1]+1;
                     else
                     {
                         int maxx=0;
                         if(maxx<dp[i-1][j])
                         maxx=dp[i-1][j];
                         if(maxx<dp[i][j-1])
                         maxx=dp[i][j-1];
                         dp[i][j]=maxx;
                     }
             }
             printf("%d
    ",dp[lens][lent]);
        }
        return 0;
    }
    

     4、最长公共递增子序列以及其路径记录问题

    给两组数字个数分别为n,m的序列,问这两组序列的最长递增公共子序列是多长,并且输出来
    数据:
    Sample Input
    5
    1 4 2 5 -12
    4
    -12 1 2 4
    Sample Output
    2
    1 4
    
    #include<iostream>
    #include<stdio.h>
    #include<string.h>
    using namespace std;
    int dp[1005][1005],a[1005],b[1005],path[1005][1005];
    int f[1005];
    int main()
    {
        int lena,lenb;
        scanf("%d",&lena);
        {
            for(int i=0;i<lena;i++)
            scanf("%d",&a[i]);
            scanf("%d",&lenb);
            for(int i=0;i<lenb;i++)
            scanf("%d",&b[i]);
            for(int i=0;i<=1004;i++)
            dp[i][0]=dp[0][i]=0;
            int tmp,ans=0;
            int qd,zd,k=0,maxx;
            for(int i=1;i<=lena;i++)
            {
                maxx=0;
                for(int j=1;j<=lenb;j++)
                {
                    dp[i][j]=dp[i-1][j];
                    if(a[i-1]>b[j-1]&&dp[i][j]>maxx)
                    {
                        maxx=dp[i][j];
                        k=j;
                    }
                    if(a[i-1]==b[j-1])
                    {
                        dp[i][j]=maxx+1;
                        path[i][j]=k;
                    }
                    //printf("%d
    ",dp[i][j]);
                    if(ans<dp[i][j])
                    {
                        ans=dp[i][j];
                        qd=i;
                        zd=j;
                    }
                }
            }
            printf("%d
    ",ans);
            int i=qd,j=zd;
            int sum=ans;
            if(ans>0)
            f[ans--]=j-1;
            while(ans&&i&&j)
            {
                if(path[i][j]>0)
                {
                    f[ans--]=path[i][j]-1;
                    j=path[i][j];
                }
                i--;
            }
            for(int i=1;i<sum;i++)
            printf("%d ",b[f[i]]);
            printf("%d
    ",b[f[sum]]);
        }
        return 0;
    }
    

     题目:

    1、poj2250(单词当字母,记忆路径)

    题意:给出两段单词,求这两段单词的最长公共单词数,并依次输出它们........

    思路:总的来说,是赤裸裸的最长公共子序列,但是需要把单词当作字母来进行.......还需要记录下路径

    #include<iostream>
    #include<stdio.h>
    #include<string.h>
    using namespace std;
    int dp[1000][1000];
    char path[200][200];
    char s[200][50],t[200][50],sum[200][50];
    int main()
    {
        while(scanf("%s",s[0])>0)
        {
             int lens=1;
             while(1)
             {
                  scanf("%s",s[lens++]);
                  if(s[lens-1][0]=='#')
                  break;
             }
             lens--;
             int lent=0;
             while(1)
             {
                  scanf("%s",t[lent++]);
                  if(t[lent-1][0]=='#')
                  break;
             }
             lent--;
             for(int i=0;i<1000;i++)
             dp[i][0]=dp[0][i]=0;
             memset(path,0,sizeof(path));
             for(int i=1;i<=lens;i++)
             {
                  for(int j=1;j<=lent;j++)
                  if(strcmp(s[i-1],t[j-1])==0)
                  {
                       dp[i][j]=dp[i-1][j-1]+1;
                       path[i][j]='1';
                  }
                  else if(dp[i-1][j]>dp[i][j-1])
                  {
                       dp[i][j]=dp[i-1][j];
                       path[i][j]='2';
                  }
                  else
                  {
                      dp[i][j]=dp[i][j-1];
                      path[i][j]='3';
                  }
             }
             int i=lens,j=lent,cnt=0;
             //printf("111
    ");
             while(path[i][j]!=0)
             {
                  int p=i,q=j;
                  if(path[p][q]=='1')   
                  p--,q--;
                  else  if(path[p][q]=='2')
                  p--;
                  else  if(path[p][q]=='3')
                  q--;
                  if(dp[i][j]!=dp[p][q])   
                  {
                       strcpy(sum[cnt++],s[i-1]);
                       //printf("%s
    ",s[i]);
                  }
                  i=p;
                  j=q;
             }
             for(int f=cnt-1;f>0;f--)
             printf("%s ",sum[f]);
             printf("%s
    ",sum[0]);
        }
        return 0;
    }
    

     2、poj1159(回文串的问题)

    题意:给你一串字符,问加最少的字符可以使它编程回文串.......

    思路:就是求出它正反的最长公共子串,然后用len减去这个长度,就是最少要加的字符数......

    3、hdu4512(最长递增公共子序列问题)

    有一天,有n个人按顺序站在他的面前,他们的身高分别是h[1], h[2] ... h[n],吉哥希望从中挑出一些人,让这些人形成一个新的队形,新的队形若满足以下三点要求,则称之为完美队形: 
      1、挑出的人保持他们在原队形的相对顺序不变;
      2、左右对称,假设有m个人形成新的队形,则第1个人和第m个人身高相同,第2个人和第m-1个人身高相同,依此类推,当然,如果m是奇数,中间那个人可以任意;
      3、从左到中间那个人,身高需保证递增,如果用H表示新队形的高度,则H[1] < H[2] < H[3] .... < H[mid]。
      现在吉哥想知道:最多能选出多少人组成完美队形?

    思路:也是把字符串倒过来正反取最长递增公共子序列,但是需要注意的是,i<j,还有,在i~~j的过程中,也许会有一个很大的数,那么也是可以加进去的,具体看代码

    #include<iostream>
    #include<stdio.h>
    #include<string.h>
    using namespace std;
    int dp[205][205],a[205];
    int main()
    {
        int text;
        scanf("%d",&text);
        while(text--)
        {
            int n;
            scanf("%d",&n);
            for(int i=0;i<n;i++)
            scanf("%d",&a[i]);
            memset(dp,0,sizeof(dp));
            int sum=0;
            for(int i=1;i<=n;i++)
            {
                int maxx=0;
                for(int j=n;j>i;j--)
                {
                    dp[i][j]=dp[i-1][j];
                    if(a[i-1]>a[j-1]&&dp[i][j]>maxx)
                    maxx=dp[i][j];
                    if(a[i-1]==a[j-1])
                    dp[i][j]=maxx+1;
                    if(dp[i][j]*2>sum)
                    sum=dp[i][j]*2;
                    for(int k=i+1;k<j;k++)
                    {
                        if(a[k-1]>a[j-1]&&dp[i][j]*2+1>sum)
                        sum=dp[i][j]*2+1;
                    }
                }    
            }
            printf("%d
    ",sum);
        }
        return 0;
    }
    

     4、hdu4512(必须距离k以上的最长递增子序列)

    提起小明序列,他给出的定义是这样的:
      ①首先定义S为一个有序序列,S={ A1 , A2 , A3 , ... , An },n为元素个数 ;
      ②然后定义Sub为S中取出的一个子序列,Sub={ Ai1 , Ai2 , Ai3 , ... , Aim },m为元素个数 ;
      ③其中Sub满足 Ai1 < Ai2 < Ai3 < ... < Aij-1 < Aij < Aij+1 < ... < Aim ;
      ④同时Sub满足对于任意相连的两个Aij-1与Aij都有 ij - ij-1 > d (1 < j <= m, d为给定的整数);
      ⑤显然满足这样的Sub子序列会有许许多多,而在取出的这些子序列Sub中,元素个数最多的称为“小明序列”(即m最大的一个Sub子序列)。
      例如:序列S={2,1,3,4} ,其中d=1;
      可得“小明序列”的m=2。即Sub={2,3}或者{2,4}或者{1,4}都是“小明序列”。
      当小明发明了“小明序列”那一刻,情绪非常激动,以至于头脑凌乱,于是他想请你来帮他算算在给定的S序列以及整数d的情况下,“小明序列”中的元素需要多少个呢?

    思路:这是一个必须距离k以上的最长递增子序列,总的来说,就是开了一个记录路径的数组,说起来说不清楚,但是很神奇的样子,看代码就可以明白的......

    #include<iostream>
    #include<stdio.h>
    #include<stdio.h>
    using namespace std;
    int dp[100005],s[100005],c[100005];
    int main()
    {
        int n,k;
        while(scanf("%d %d",&n,&k)>0)
        {
            for(int i=1;i<=n;i++)
            {
                scanf("%d",&s[i]);
                c[i]=10000000;
            }
            int len=0;
            for(int i=1;i<=n;i++)
            {
                int left=1,right=n,mid;
                while(left<=right)
                {
                    mid=(left+right)/2;
                    if(s[i]>c[mid])
                    left=mid+1;
                    else  right=mid-1;
                }
                dp[i]=left;
                //printf("dp==%d
    ",left);
                if(dp[i]>len)
                len=dp[i];
                int j=i-k;
                if(j>0&&c[dp[j]]>s[j])
                c[dp[j]]=s[j];
                //printf("c==%d
    ",c[dp[j]]);
            }
            printf("%d
    ",len);
        }
        return 0;
    } 
    

     5、hdu4681(需要正反预处理的最长公共子序列)

    题意:给你A,B,C三个字符串,其中A和B都包含C,现在要找这样一个D串,D串要包含C串,并且是A和B串的最长子串.......

    超时思路:在A与B串中枚举C串的位置,然后截取掉,然后就是A串的开头与B串的开头的最长公共子序列,再是A串的结尾与B串的结尾的最长公共子序列,再加上C串的长度.....

    ac思路:做预处理,先对A和B正序做一次最长公共子序列,再对A和B反序做一次最长公共子序列,然后分别在A串和B串中枚举C串出现的各个起始位置,记录下来,在进行比较,选择最长的......当然,要注意,反序的dp中,字符出现的位置要处理好......

    #include<iostream>
    #include<stdio.h>
    #include<string.h>
    #include<algorithm>
    using namespace std;
    char s[1005],t[1005],f[1005],s1[1005],t1[1005];
    int dp1[1005][1005],dp2[1005][1005];
    int cnts=0;
    int cntt=0;
    int lens,lent,lenf;
    int deal(int beg[],int end[],char str[])
    {
        int len=strlen(str);
        int flag=0;
        for(int i=0;i<len;i++)
        {
            int p1=0,p2=i;
            if(str[p2]==f[p1])
            {
                while(p1<lenf&&p2<len)
                {
                    if(str[p2]==f[p1])
                    {
                        p1++;
                        p2++;
                    }
                    else
                    p2++;
                }
                if(p1==lenf)
                {
                    beg[flag]=i;
                    end[flag++]=p2-1;
                }
            }
        }
        return flag;
    }
    int main()
    {
        int text,kp=0;
        scanf("%d",&text);
        while(text--)
        {
            scanf("%s",s);
            scanf("%s",t);
            scanf("%s",f);
            memset(dp1,0,sizeof(dp1));
            memset(dp2,0,sizeof(dp1));
            lens=strlen(s);
            lent=strlen(t);
            lenf=strlen(f);
            for(int i=1;i<=lens;i++)
            {
                for(int j=1;j<=lent;j++)
                if(s[i-1]==t[j-1])
                dp1[i][j]=dp1[i-1][j-1]+1;
                else
                {
                    int maxx=0;
                    if(maxx<dp1[i-1][j])
                    maxx=dp1[i-1][j];
                    if(maxx<dp1[i][j-1])
                    maxx=dp1[i][j-1];
                    dp1[i][j]=maxx;
                }
             }
             int cnt=0;
             for(int i=lens-1;i>=0;i--)
             s1[cnt++]=s[i];
             cnt=0;
             for(int i=lent-1;i>=0;i--)
             t1[cnt++]=t[i];
             for(int i=1;i<=lens;i++)
             {
                for(int j=1;j<=lent;j++)
                if(s1[i-1]==t1[j-1])
                dp2[i][j]=dp2[i-1][j-1]+1;
                else
                {
                    int maxx=0;
                    if(maxx<dp2[i-1][j])
                    maxx=dp2[i-1][j];
                    if(maxx<dp2[i][j-1])
                    maxx=dp2[i][j-1];
                    dp2[i][j]=maxx;
                }
             }
             int beg1[1005],end1[1005];
             int beg2[1005],end2[1005];
             cnts=deal(beg1,end1,s);
             cntt=deal(beg2,end2,t);
             int maxn=0;
             //for(int i=0;i<cnts;i++)
             //printf("%d %d
    ",beg1[i],end1[i]);
             for(int i=0;i<cnts;i++)
             for(int j=0;j<cntt;j++)
             if(maxn<dp1[beg1[i]+1][beg2[j]+1]+dp2[lens-end1[i]-1][lent-end2[j]-1]+lenf)
             maxn=dp1[beg1[i]+1][beg2[j]+1]+dp2[lens-end1[i]-1][lent-end2[j]-1]+lenf;
             printf("Case #%d: %d
    ",++kp,maxn-1);
        }
        return 0;
    }
    

     6、uva10635

    题意:给你两个数组,A和B,每个数组里面的元素是唯一的,求这两个数组的最长公共子序列........

    思路:这个题目数据量很大,求最长公共子序列要o(n*n)会超时,其实可以转换为求最长递增子序列,然后可以在O(nlogn)的时间复杂度求解........

    就是,给A中的元素一次编号,在输入B中元素的时候,先判断A中是否有这个元素,没有的话,直接过滤,有的话,把这个元素在A中的编号记录在B数组,然后对B求一次最长递增子序列.........

    #include<iostream>
    #include<stdio.h>
    #include<string.h>
    using namespace std;
    int a[100000],b[100000],dp[100000];
    int main()
    {
        int text,f=0;
        scanf("%d",&text);
        while(text--)
        {
            int n,p,q;
            scanf("%d%d%d",&n,&p,&q);
            memset(a,0,sizeof(a));
            for(int i=1;i<=p+1;i++)
            {
                int tmp;
                scanf("%d",&tmp);
                a[tmp]=i;
            }
            int cnt=0;
            for(int i=1;i<=q+1;i++)
            {
                int tmp;
                scanf("%d",&tmp);
                if(a[tmp]==0)  continue;
                b[cnt++]=a[tmp];
            }
            int len=1;
            //for(int i=0;i<cnt;i++)
            //printf("%d
    ",b[i]);
            dp[0]=b[0];
            for(int i=1;i<cnt;i++)
            {
                int left=0,right=len-1,mid;
                if(dp[len-1]<b[i])
                dp[len++]=b[i];
                else
                {
                    right=len-1;
                    while(left<=right)
                    {
                        mid=(left+right)/2;
                        if(dp[mid]<b[i])
                        left=mid+1;
                        else  right=mid-1;
                    }
                    dp[left]=b[i];
                }
            }
            printf("Case %d: %d
    ",++f,len);
        }
        return 0;
    }
    

     7、poj1952(求子序列个数)

    题意:求最长递减(严格递减)子序列长度,并输出不相同的最长递减子序列的个数

    思路:

    题解:两次DP。首先求出最长的递减子序列的长度,状态转移方程为dp[i]=max(dp[j])+1;(0<=j<i);(下标从0开始)其中dp[i]表示以第i个数结尾的最长递减子序列的长度。这里在最后加一个dp[n]=-1,具体为什么在下一步讲述。

             然后求每一种长度的递减子序列共有几个。状态转移方程:count[i]=sum(coun[j]);(其中a[i]<a[j]&&dp[j]+1==dp[i]);count[i]是以a[i]结尾的以dp[i]长度的递减子序列的个数。这里要注意排除子序列相同的情况!这里给出一个例子加以说明:

                                    i  0  1  2  3  4  5   6

                                a[i]  5  8  4  4  3  2  -1

                              dp[i]  1  1  2  2  3  4   5

                         count[i]   1  1  2  2  2  2   2

           这里在统计count[4]时,当加上count[3]后要忽略count[2]。

          还有在数组最后加上a[n]=-1是保证count[n]一定是最终的答案,而不需要递推完count[]后再一一查找出最大的count[]中的最大值。

    上面如果理解了dp[]和count[]的数组的意义之后此题的解法就很明了了。

    代码:

    #include<iostream>
    #include<stdio.h>
    #include<string.h>
    using namespace std;
    int dp[5005],cont[5005],a[5005];
    int main()
    {
        int n;
        //scanf("%d",&text);
        while(scanf("%d",&n)>0)
        {
            for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
            a[n+1]=-100000;
            for(int i=0;i<=n+1;i++)
            {
                dp[i]=1;
                //cont[i]=1;
            }
            int maxn1=0;
            for(int i=1;i<=n+1;i++)
            {
                for(int j=1;j<i;j++)
                if(a[i]<a[j]&&dp[i]<dp[j]+1)
                dp[i]=dp[j]+1;
                if(dp[i]>maxn1)
                maxn1=dp[i];
            }
            int flag=0;
            for(int i=1;i<=n+1;i++)
            {
                if(dp[i]==1)
                {
                   cont[i]=1;
                   continue;
                }
                else
                cont[i]=0;
                for(int j=i-1;j>=1;j--)
                {
                    if(a[i]<a[j]&&dp[i]==dp[j]+1)
                    {
                        flag=1;
                        for(int k=j+1;k<i;k++)
                        {
                            if(a[k]==a[j])
                            {
                                flag=0;
                                break;
                            }
                        }
                        if(flag)
                        cont[i]+=cont[j];
                    }
                }
            }
            printf("%d %d
    ",maxn1-1,cont[n+1]);
        }
        return 0;
    }
    
  • 相关阅读:
    go包之logrus显示日志文件与行号
    linux几种快速清空文件内容的方法
    (转)CSS3之pointer-events(屏蔽鼠标事件)属性说明
    Linux下source命令详解
    控制台操作mysql常用命令
    解决beego中同时开启http和https时,https端口占用问题
    有关亚马逊云的使用链接收集
    favicon.ico--网站标题小图片二三事
    js获取url协议、url, 端口号等信息路由信息
    (转) Golang的单引号、双引号与反引号
  • 原文地址:https://www.cnblogs.com/ziyi--caolu/p/3235229.html
Copyright © 2011-2022 走看看