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\n",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\n",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\n",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\n",dp[i][j]);
                if(ans<dp[i][j])
                {
                    ans=dp[i][j];
                    qd=i;
                    zd=j;
                }
            }
        }
        printf("%d\n",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\n",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\n");
         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\n",s[i]);
              }
              i=p;
              j=q;
         }
         for(int f=cnt-1;f>0;f--)
         printf("%s ",sum[f]);
         printf("%s\n",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\n",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\n",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\n",c[dp[j]]);
        }
        printf("%d\n",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\n",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\n",++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\n",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\n",++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\n",maxn1-1,cont[n+1]);
    }
    return 0;
}

 

posted @ 2013-08-03 21:03  紫忆  阅读(1819)  评论(0编辑  收藏  举报