1007 - 最长公共子序列(输出该子序列及其长度)
有一天夜晚,我烧毁了所有的记忆,从此我的梦透明了;有一个早晨,我扔掉了所有的昨天,从此我的脚步就轻盈了。
--泰戈尔
今天是国庆的最后一天了,感觉时间过的好快,待在机房的时间总是那么短暂,从明天开始就要停课了
愿自己丢掉所有包袱,好好地冲刺今年的NOIP,不负韶华,不负青春
其实如果光论最长公共子序列的长度,线性dp,最最基础的,就可以了
但如果还要求我们多一项任务:输出最长公共子序列
就需要再想想咯
先看一道题吧:
最长公共子序列
描述
一个给定序列的子序列是在该序列中删去若干元素后得到的序列。确切地说,若给定序列X=<x1,x2,…,xm>,则另一序列Z=<z1,z2,…,zk>是X的子序列是指存在一个严格递增的下标序列<i1,i2,…,ik>,使得对于所有j=1,2,…,k有:Xij=Zj
例如,序列z=<B,C,D,B>是序列X=<A,B,C,B,D,A,B>的子序列,相应的递增下标序列为<2,3,5,7>。给定两个序列X和Y,当另一序列Z既是X的子序列又是Y的子序列时,称z是序列x和Y的公共子序列。例如,若x=<A,B,C,B,D,A,B>和Y=<B,D,C,A,B,A>,则序列<B,C,A>是X和Y的一个公共子序列,序列 <B,C,B,A>也是X和Y的一个公共子序列。而且,后者是X和Y的一个最长公共子序列.因为x和Y没有长度大于4的公共子序列。
给定两个序列X=<x1,x2,…,xm>和Y=<y1,y2….yn>.要求找出X和Y的一个最长公共子序列。
输入
输入文件共有两行。每行为一个由大写字母构成的长度不超过200的字符串,表示序列X和Y
输出
输出文件第一行为一个非负整数。表示所求得的最长公共子序列的长度。若不存在公共子序列.则输出文件仅有一行输出一个整数0。否则在输出文件的第二行输出所求得的最长公共子序列(也用一个大写字母组成的字符串表示)。若符合条件的最长公共子序列不止一个,只需输出其中任意的一个。
样例输入
ABCBDAB
BDCABA
样例输出
4
BCBA
分析
对于长度,我们用一个基础的线性dp来搞
定义 f[i][j] 表示处理了 a 字符串的1~ i 和 b 字符串的 1 ~ j ,最长的公共子序列长度是多少
转移方程:
如果当前的 a[i] == b[j] 则多一步
这些应该都很好理解,最后的答案就存在:f [ strlen(a+1) ] [ strlen(b+1) ]中,表示处理完 a 字符串和 b 字符串后得到的最长长度
现在我们来讲一下如何输出最长的LCS(借鉴网上的一张表)
我们可以在转移的时候记录下,当前状态是由之前的哪一个状态转移而来,最后输出的时候就从后往前递归处理
代码
//虽然我很菜,但我也会很努力!
#include<bits/stdc++.h>
using namespace std;
char a[205],b[205];
int f[205][205],g[205][205];//g存储当前状态是由之前三种可能的哪一种转移而来
void getans(int n,int m){
if(!n||!m) return;
if(g[n][m]==1){//只有这种情况才需要输出,结合我给的表就清楚了
getans(n-1,m-1);
printf("%c",a[n]);
}
else if(g[n][m]==-1) getans(n-1,m);
else getans(n,m-1);
}
int main(){
scanf("%s%s",a+1,b+1);
int lena=strlen(a+1),lenb=strlen(b+1);
for(int i=1;i<=lena;++i){
for(int j=1;j<=lenb;++j){
if(f[i-1][j]>f[i][j]) f[i][j]=f[i-1][j],g[i][j]=-1;
if(f[i][j-1]>f[i][j]) f[i][j]=f[i][j-1],g[i][j]=0;
if(a[i]==b[j]){
if(f[i-1][j-1]+1>f[i][j]){
g[i][j]=1;
f[i][j]=f[i-1][j-1]+1;
}
}
}
}
if(!f[lena][lenb]) printf("0");
else{
printf("%d\n",f[lena][lenb]);
getans(lena,lenb);
}
return 0;
}