最长公共子序列(LCS)、最长递增子序列(LIS)、最长递增公共子序列(LICS)

最长公共子序列(LCS)

【问题】 求两字符序列的最长公共字符子序列

问题描述:字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。令给定的字符序列X=“x0,x1,…,xm-1”,序列Y=“y0,y1,…,yk-1”是X的子序列,存在X的一个严格递增下标序列<i0,i1,…,ik-1>,使得对所有的j=0,1,…,k-1,有xij=yj。例如,X=“ABCBDAB”,Y=“BCDB”是X的一个子序列。

考虑最长公共子序列问题如何分解成子问题,设A=“a0,a1,…,am-1”,B=“b0,b1,…,bm-1”,并Z=“z0,z1,…,zk-1”为它们的最长公共子序列。不难证明有以下性质:

(1) 如果am-1=bn-1,则zk-1=am-1=bn-1,且“z0,z1,…,zk-2”是“a0,a1,…,am-2”和“b0,b1,…,bn-2”的一个最长公共子序列;

(2) 如果am-1!=bn-1,则若zk-1!=am-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列;

(3) 如果am-1!=bn-1,则若zk-1!=bn-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列。

这样,在找A和B的公共子序列时,如有am-1=bn-1,则进一步解决一个子问题,找“a0,a1,…,am-2”和“b0,b1,…,bm-2”的一个最长公共子序列;如果am-1!=bn-1,则要解决两个子问题,找出“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列和找出“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列,再取两者中较长者作为A和B的最长公共子序列。

求解:

引进一个二维数组c[][],用c[i][j]记录X[i]与Y[j] 的LCS 的长度,b[i][j]记录c[i][j]是通过哪一个子问题的值求得的,以决定搜索的方向。
我们是自底向上进行递推计算,那么在计算c[i,j]之前,c[i-1][j-1],c[i-1][j]与c[i][j-1]均已计算出来。此时我们根据X[i] = Y[j]还是X[i] != Y[j],就可以计算出c[i][j]。

问题的递归式写成:

回溯输出最长公共子序列过程:

算法分析:
由于每次调用至少向上或向左(或向上向左同时)移动一步,故最多调用(m + n)次就会遇到i = 0或j = 0的情况,此时开始返回。返回时与递归调用时方向相反,步数相同,故算法时间复杂度为Θ(m + n)。

那么以HDU 1159来做演示、

 1 #include<cstring>
 2 #include<cmath>
 3 #include<cstring>
 4 #include<cstdio>
 5 const int qq=1e3+10;
 6 char x[qq],y[qq];
 7 int dp[qq][qq];
 8 int main()
 9 {
10     while(~scanf("%s%s",x+1,y+1)){
11         x[0]=y[0]=‘.‘;
12         int len=strlen(x)>strlen(y)?strlen(x):strlen(y);
13     //    printf("%d %d\n",strlen(x),strlen(y));
14         for(int i=0;i<=len;++i)
15             dp[i][0]=dp[0][i]=0;
16         for(int j,i=1;i<strlen(x);++i)
17             for(j=1;j<strlen(y);++j)
18                 if(x[i]==y[j])
19                     dp[i][j]=dp[i-1][j-1]+1;
20                 else
21                     dp[i][j]=dp[i-1][j]>dp[i][j-1]?dp[i-1][j]:dp[i][j-1];
22         printf("%d\n",dp[strlen(x)-1][strlen(y)-1]);
23     }
24     return 0;
25 } 

最长递增子序列(LIS)

以POJ 2533来讲解吧、

现在给你一个序列,要你求最长上升子序列,那么把这个序列排一个序,然后和原有序列进行LCS 是不是就解决问题了呢?

当然还有dp思路了、

dp[ i ]以序列中第i个元素结尾的最长上升子序列的长度

那么状态转移方程为:if (a[i] > a[j]) dp[i] = MAX (dp[i], dp[j] + 1);

 1 #include<cstdio>
 2 #include<cmath>
 3 #include<cstring>
 4 const int qq=1005;
 5 int dp[qq];
 6 int num[qq];
 7 int main()
 8 {
 9     int n;
10     while(~scanf("%d",&n)){
11         memset(dp,0,sizeof(dp));        // 5 6 7 8 1
12         for(int i=0;i<n;++i)
13         scanf("%d",&num[i]);
14         dp[0]=1;
15         int x=0;
16         for(int j,i=0;i<n;++i){
17             int maxn=0;
18             for(j=0;j<i;++j)    //这里利用了递推的原理、
19                 if(num[i]>num[j])    //由前面的最长递增子序列推出后面的最长递增子序列、
20                     maxn=maxn>dp[j]?maxn:dp[j];
21             dp[i]=maxn+1;
22             if(dp[i]>x)    x=dp[i];    //x记录的是当前的最大值、
23         }
24         printf("%d\n",x);
25     }
26     return 0;
27 }

对于LIS还有一种O(n*logn)的做法、

这里以HDU 1025为例、

转载自 kuangbin

假设要寻找最长上升子序列的序列是a[n],然后寻找到的递增子序列放入到数组b中。

(1)当遍历到数组a的第一个元素的时候,就将这个元素放入到b数组中,以后遍历到的元素都和已经放入到b数组中的元素进行比较;

(2)如果比b数组中的每个元素都大,则将该元素插入到b数组的最后一个元素,并且b数组的长度要加1;

(3)如果比b数组中最后一个元素小,就要运用二分法进行查找,查找出第一个比该元素大的最小的元素,然后将其替换。

在这个过程中,只重复执行这两步就可以了,最后b数组的长度就是最长的上升子序列长度。例如:如该数列为:

5 9 4 1 3 7 6 7

那么:

5 //加入
5 9 //加入
4 9 //用4代替了5
1 9 //用1代替4
1 3 //用3代替9
1 3 7 //加入
1 3 6 //用6代替7
1 3 6 7 //加入

最后b中元素的个数就是最长递增子序列的大小,即4。

要注意的是最后数组里的元素并不就一定是所求的序列,

例如如果输入 2 5 1

那么最后得到的数组应该是 1 5

而实际上要求的序列是 2 5

 1 #include<iostream>
 2 #include<algorithm>
 3 #include<cstdio>
 4 #include<cstring>
 5 const int qq=500000+5;
 6 int city[qq];
 7 int dp[qq]={0};
 8 int search(int x,int l,int r)
 9 {            //二分查找找到一个位置,使得x>dp[i-1], 并且x<dp[i],并用x代替dp[i]
10     int m;
11     while(l<=r){
12         m=(l+r)>>1;
13         if(x>=dp[m])    l=m+1;
14         else            r=m-1;
15     }
16     return l;
17 }
18 int DP(int n)
19 {
20     int i,len;
21     dp[1]=city[1];
22     len=1;
23     for(int i=2;i<=n;++i){
24         if(city[i]>=dp[len]){
25             len=len+1;
26             dp[len]=city[i];
27         }
28         else{
29             int temp=search(city[i],1,len);
30             dp[temp]=city[i];
31         }
32     }
33     return len;
34 }
35 int main()
36 {
37     int n;
38     int t=1;
39     while(~scanf("%d",&n)){
40         int a,b;
41         for(int i=0;i<n;++i){
42             scanf("%d%d",&a,&b);
43             city[a]=b;
44         }
45         int maxn=DP(n);
46         printf("Case %d:\n",t++);
47         if(maxn==1)    printf("My king, at most 1 road can be built.\n\n");//注意这里road的复数问题,超级坑
48         else    printf("My king, at most %d roads can be built.\n\n",maxn);
49         memset(dp,0,sizeof(dp));
50     }
51     return 0;
52 }

最长递增公共子序列(LICS)

转载:传送门

那么以ZOJ的2432为例、

最长公共上升子序列(LCIS)的O(n^2)算法

预备知识:动态规划的基本思想,LCS,LIS。

问题:字符串a,字符串b,求a和b的LCIS(最长公共上升子序列)。

首先我们可以看到,这个问题具有相当多的重叠子问题。于是我们想到用DP搞。DP的首要任务是什么?定义状态。

1定义状态F[i][j]表示以a串的前i个字符b串的前j个字符且以b[j]为结尾构成的LCIS的长度。

为什么是这个而不是其他的状态定义?最重要的原因是我只会这个,还有一个原因是我知道这个定义能搞到平方的算法。而我这只会这个的原因是,这个状态定义实在是太好用了。这一点我后面再说。

我们来考察一下这个这个状态。思考这个状态能转移到哪些状态似乎有些棘手,如果把思路逆转一下,考察这个状态的最优值依赖于哪些状态,就容易许多了。这个状态依赖于哪些状态呢?

首先,在a[i]!=b[j]的时候有F[i][j]=F[i-1][j]。为什么呢?因为F[i][j]是以b[j]为结尾的LCIS,如果F[i][j]>0那么就说明a[1]..a[i]中必然有一个字符a[k]等于b[j](如果F[i][j]等于0呢?那赋值与否都没有什么影响了)。因为a[k]!=a[i],那么a[i]对F[i][j]没有贡献,于是我们不考虑它照样能得出F[i][j]的最优值。所以在a[i]!=b[j]的情况下必然有F[i][j]=F[i-1][j]。这一点参考LCS的处理方法。

那如果a[i]==b[j]呢?首先,这个等于起码保证了长度为1的LCIS。然后我们还需要去找一个最长的且能让b[j]接在其末尾的LCIS。之前最长的LCIS在哪呢?首先我们要去找的F数组的第一维必然是i-1。因为i已经拿去和b[j]配对去了,不能用了。并且也不能是i-2,因为i-1必然比i-2更优。第二维呢?那就需要枚举b[1]..b[j-1]了,因为你不知道这里面哪个最长且哪个小于b[j]。这里还有一个问题,可不可能不配对呢?也就是在a[i]==b[j]的情况下,需不需要考虑F[i][j]=F[i-1][j]的决策呢?答案是不需要。因为如果b[j]不和a[i]配对,那就是和之前的a[1]..a[j-1]配对(假设F[i-1][j]>0,等于0不考虑),这样必然没有和a[i]配对优越。(为什么必然呢?因为b[j]和a[i]配对之后的转移是max(F[i-1][k])+1,而和之前的i`配对则是max(F[i`-1][k])+1。显然有F[i][j]>F[i`][j],i`>i)

于是我们得出了状态转移方程:

a[i]!=b[j]:   F[i][j]=F[i-1][j]

a[i]==b[j]:   F[i][j]=max(F[i-1][k])+1 1<=k<=j-1&&b[j]>b[k]

不难看到,这是一个时间复杂度为O(n^3)的DP,离平方还有一段距离。

但是,这个算法最关键的是,如果按照一个合理的递推顺序,max(F[i-1][k])的值我们可以在之前访问F[i][k]的时候通过维护更新一个max变量得到。怎么得到呢?首先递推的顺序必须是状态的第一维在外层循环,第二维在内层循环。也就是算好了F[1][len(b)]再去算F[2][1]。

如果按照这个递推顺序我们可以在每次外层循环的开始加上令一个max变量为0,然后开始内层循环。当a[i]>b[j]的时候令max=F[i-1][j]。如果循环到了a[i]==b[j]的时候,则令F[i][j]=max+1。

最后答案是F[len(a)][1]..F[len(a)][len(b)]的最大值。

 1 #include<cmath>
 2 #include<cstdio>
 3 #include<cstring>
 4 const int qq=505;
 5 int a[qq],b[qq];
 6 int dp[qq][qq];
 7 int prex[qq][qq];
 8 int prey[qq][qq];
 9 int count,ans;
10 void out(int x,int y)    //递归输出路径、
11 {
12     if(dp[x][y]==0)    return;
13     int xx=prex[x][y];
14     int yy=prey[x][y];
15     out(xx,yy);
16     if(dp[x][y]!=dp[xx][yy] && y!=0){
17         printf("%d",b[y]);
18         count++;
19         if(count<ans)    printf(" ");
20         else            printf("\n");
21     }
22 }
23 int main()
24 {
25     int t;scanf("%d",&t);
26     while(t--){
27         int n;
28         scanf("%d",&n);
29         for(int i=1;i<=n;++i)
30             scanf("%d",&a[i]);
31         int m;
32         scanf("%d",&m);
33         for(int i=1;i<=m;++i)
34             scanf("%d",&b[i]);
35         memset(prex,-1,sizeof(prex));
36         memset(prey,-1,sizeof(prey));
37         memset(dp,0,sizeof(dp));
38         for(int j,i=1;i<=n;++i){
39             int maxn=0;
40             int x,y;x=y=0;
41             for(j=1;j<=m;++j){
42                 dp[i][j]=dp[i-1][j];        //先更新状态,如果后面会更新的话再去更新
43                 prex[i][j]=i-1;
44                 prey[i][j]=j;
45                 if(a[i]>b[j] && maxn<dp[i-1][j]){    //我开始一直没想通为什么要在a[i]>b[j]的时候才更新值
46                     maxn=dp[i-1][j];// 其实你想想dp[i][j]中i,j都有什么含义、
47                     x=i-1;        // 这个if里面更新出来的maxn只有在满足a[i]==b[j]的时候才有用、
48                     y=j;    //  这里更新出来的值就是为了保证当a[i]==b[j]的时候
49                 }            //所更新出来的最大值是一个递增的子序列、
50                 else if(a[i]==b[j]){
51                     dp[i][j]=maxn+1;
52                     prex[i][j]=x;
53                     prey[i][j]=y;
54                 }
55             }
56         }
57         for(int i=1;i<=n;++i){
58             for(int j=1;j<=m;++j)
59                 printf("%d ",dp[i][j]);
60             printf("\n");
61         }
62         ans=0;
63         int flag=-1;
64         for(int i=1;i<=m;++i){
65             if(ans<dp[n][i]){
66                 flag=i;
67                 ans=dp[n][i];
68             }
69         }
70         printf("%d\n",ans);
71         int x=n,y=flag;
72         count=0;
73         if(ans>0)
74             out(x,y);
75         if(t)    printf("\n");
76     }
77     return 0;
78 }
时间: 2024-10-10 16:35:45

最长公共子序列(LCS)、最长递增子序列(LIS)、最长递增公共子序列(LICS)的相关文章

[2016-05-09][51nod][1006 最长公共子序列Lcs]

时间:2016-05-09 21:12:54 星期一 题目编号:[2016-05-09][51nod][1006 最长公共子序列Lcs] 题目大意:[2016-05-09][51nod][1006 最长公共子序列Lcs].md 分析:动态规划 dp[i][j] 表示字符串A以第i个位置 ,字符串B以第j个位置的最长公共子序列的长度 dp[i][j] = dp[i - 1][j - 1] + 1 if a[i] == a[j] else dp[i][j] == max(dp[i - 1][j] ,

算法设计 - LCS 最长公共子序列&amp;&amp;最长公共子串 &amp;&amp;LIS 最长递增子序列

出处 http://segmentfault.com/blog/exploring/ 本章讲解:1. LCS(最长公共子序列)O(n^2)的时间复杂度,O(n^2)的空间复杂度:2. 与之类似但不同的最长公共子串方法.最长公共子串用动态规划可实现O(n^2)的时间复杂度,O(n^2)的空间复杂度:还可以进一步优化,用后缀数组的方法优化成线性时间O(nlogn):空间也可以用其他方法优化成线性.3.LIS(最长递增序列)DP方法可实现O(n^2)的时间复杂度,进一步优化最佳可达到O(nlogn)

算法重拾之路——最长公共子序列(LCS)

***************************************转载请注明出处:http://blog.csdn.net/lttree******************************************** 第二章:动态规划 最长公共子序列 算法描述: 一个给定序列的子序列是该序列中删去若干元素后得到的序列.确切的说,若给定序列 X={ x1,x2,...,xm },则另一序列 Z = { z1,z2, ... ,zk },是X的子序列是指存在一个严格递增下标序列

编程算法 - 最长公共子序列(LCS) 代码(C)

最长公共子序列(LCS) 代码(C) 本文地址: http://blog.csdn.net/caroline_wendy 题目: 给定两个字符串s,t, 求出这两个字符串最长的公共子序列的长度. 字符串的子序列并一定要连续, 能够包含间隔. 即最长公共子序列问题(LCS, Longest Common Subsequence) 使用动态规划, 假设字符相等, 两个字符串就依次递增一位, 一直到字符串的结尾. 代码: /* * main.cpp * * Created on: 2014.7.17

动态规划-最长公共子序列LCS

0 问题 给定两个字符串,求最长公共子序列LCS. 也就是说两个字符串中都有的部分,或者理解为,两个字符串同时都删除字符串中的某些字符,使得最终的两个字符串,相等,且是最长的. 1 分析 假设两个str1,str2字符串,已经知道了最长公共子序列长度为L 那么,当在str1和str2,两个的尾部,同时添加一个相同的字符,比如a,那么新的str1,和str2的最长公共子序列长度就是L+1 当str1后面添加一个字符,str2不添加,那么最长公共子序列长度为L 反之,str1不添加,str2添加,那

1006 最长公共子序列Lcs

1006 最长公共子序列Lcs 基准时间限制:1 秒 空间限制:131072 KB 给出两个字符串A B,求A与B的最长公共子序列(子序列不要求是连续的). 比如两个串为: abcicba abdkscab ab是两个串的子序列,abc也是,abca也是,其中abca是这两个字符串最长的子序列. Input 第1行:字符串A 第2行:字符串B (A,B的长度 <= 1000) Output 输出最长的子序列,如果有多个,随意输出1个. Input示例 abcicba abdkscab Outpu

POJ 1458 Common Subsequence(最长公共子序列LCS)

POJ1458 Common Subsequence(最长公共子序列LCS) http://poj.org/problem?id=1458 题意: 给你两个字符串, 要你求出两个字符串的最长公共子序列长度. 分析: 本题不用输出子序列,非常easy,直接处理就可以. 首先令dp[i][j]==x表示A串的前i个字符和B串的前j个字符的最长公共子序列长度为x. 初始化: dp全为0. 状态转移: IfA[i]==B[j] then dp[i][j]= dp[i-1][j-1]+1 else dp[

序列最的问题之最长公共子序列LCS

在程序设计竞赛中,我们时常会遇到序列求最值的问题.在讲今天的问题之前,先小小的说明一下,子序列与子串的问题. 子序列:在原序列中不一定连续: 子串:在原序列中必须连续. 接下来,就开始今天要讲的最长公共子序列LCS(Longest Common Subsequence).对于LCS这一类的问题,一般是相对于两个序列而言,str[]与ch[].先假设str的长度为n,ch的长度为m.假设str[]="ASBDAH",ch[]="SDAAH";其中"SDA&q

动态规划算法解最长公共子序列LCS问题

第一部分.什么是动态规划算法 ok,咱们先来了解下什么是动态规划算法. 动态规划一般也只能应用于有最优子结构的问题.最优子结构的意思是局部最优解能决定全局最优解(对有些问题这个要求并不能完全满足,故有时需要引入一定的近似).简单地说,问题能够分解成子问题来解决. 动态规划算法分以下4个步骤: 描述最优解的结构 递归定义最优解的值 按自底向上的方式计算最优解的值   //此3步构成动态规划解的基础. 由计算出的结果构造一个最优解.   //此步如果只要求计算最优解的值时,可省略. 好,接下来,咱们

51nod 1006 最长公共子序列Lcs(dp+string,无标记数组实现)

1006 最长公共子序列Lcs 基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题 收藏 关注 取消关注 给出两个字符串A B,求A与B的最长公共子序列(子序列不要求是连续的). 比如两个串为: abcicba abdkscab ab是两个串的子序列,abc也是,abca也是,其中abca是这两个字符串最长的子序列. Input 第1行:字符串A 第2行:字符串B (A,B的长度 <= 1000) Output 输出最长的子序列,如果有多个,随意输出1个. Input示