主要是最长公共子序列和最长上升子序列。
目录
一.最长公共子序列
1.基本操作
问题:给定序列a、b,求a、b的最长公共子序列的长度。
直接dp[i][j]表示直到a的第i项,b的第j项的最长公共子序列。
dp[i][j]=max(dp[i][j-1],dp[i-1][j])(a[i]!=b[j])
dp[i][j]=dp[i-1][j-1]+1(a[i]==b[j])
2.求最长公共子序列个数
【HAOI2010 Day2】最长公共子序列
问题描述
字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。令给定的字符序列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=“BCBD”是X的一个子序列。
对给定的两个字符序列,求出他们最长的公共子序列长度,以及最长公共子序列个数。
设dp[i][j]表示直到a的第i项,b的第j项的最长公共子序列,f[i][j]表示直到a的第i项,b的第j项的最长公共子序列的个数。
dp[i][j]=max(dp[i][j-1],dp[i-1][j])(a[i]!=b[j])
dp[i][j]=dp[i-1][j-1]+1(a[i]==b[j])
f[i][j]=f[i-1][j-1](a[i]==b[j])
f[i][j]+=f[i-1][j](dp[i][j]==dp[i-1][j])
f[i][j]+=f[i][j-1](dp[i][j]==dp[i][j-1])
f[i][j]-=f[i-1][j-1](a[i]!=b[j]&&dp[i-1][j-1]==dp[i][j])
f的设法很好想,关键是如何去重?
感性理解一下,如果有(a[i]!=b[j]&&dp[i-1][j-1]==dp[i][j]),那么f[i][j]将加上两次f[i-1][j-1],便需要减去。
3.求最长公共子序列(字典序版)
最长公共子序列(字典序最小版)
问题描述
求字符串a和b的最长公共子序列(lcs)。输出字典序最小的方案。
先求出最长公共子序列的长度,然后凑出字典序最小或最大。因为小写字母总共26个,所以对于ans数组的第i位去枚举放谁(乘上一个26的常数)。
于是初始化f[i][j]表示自i+1位起到结尾,字母(char)('a'-j+1)第一次出现位置的下标(是一个贪心)。每次枚举到ans的第i位,判断字母j:
dp[nexta][nextb]>=ans-i?( nexta=f[nowa][j],nextb=f[nowb][j])
是就转移括号内的,否则j++
代码:
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<queue>
#include<stack>
using namespace std;
const int N=5e3+5,M=30;
int n,m;
char a[N],b[N],c[N];
int ta[N][M],tb[N][M],dp[N][N];
void init()
{
for(int i=1;i<=26;i++)
{
ta[n+1][i]=N,tb[m+1][i]=N;
for(int j=n;j>=1;j--)
{
ta[j][i]=ta[j+1][i];
if(a[j]==(char)'a'+i-1)ta[j][i]=j;
}
for(int j=m;j>=1;j--)
{
tb[j][i]=tb[j+1][i];
if(b[j]=='a'+i-1)tb[j][i]=j;
}
}
}
int main()
{
scanf("%d%d",&n,&m);
scanf("%s%s",a+1,b+1);
for(int i=n;i>=1;i--)
{
for(int j=m;j>=1;j--)
{
dp[i][j]=max(dp[i+1][j],dp[i][j+1]);
if(a[i]==b[j])dp[i][j]=dp[i+1][j+1]+1;
}
}
init();
int ans=dp[1][1],ax=0,bx=0;
for(int i=1;i<=ans;i++)
{
for(int j=1;j<=26;j++)
{
int nax=ta[ax+1][j],nbx=tb[bx+1][j];
if(nax==N||nbx==N)continue;
if(dp[nax][nbx]>=ans-i+1)
{
ax=nax,bx=nbx;
c[i]=(char)('a'+j-1);
break;
}
}
}
for(int i=1;i<=ans;i++)
{
printf("%c",c[i]);
}
return 0;
}
总结:对于求字典序最大或最小问题,dp[i][j]表示自a的i位,b的j位到结尾的最长公共子序列长度大小,方便ans数组的求解(从后往前)。
最短包含串
问题描述
给定字符串a和b,求最短的字符串s,使得a与b均为s的子序列。
仅求长度太简单了,所以你要找到所有满足条件的s中字典序最小的。
同理,只是变了一下。
求出答案anslen=lena+lenb-dp[1][1]。dp设法同上一道题,倒着存。
代码:
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<queue>
#include<stack>
using namespace std;
const int N=5e3+5,M=30;
int n,m;
int dp[N][N];
char a[N],b[N],c[2*N];
int main()
{
scanf("%d%d",&n,&m);
scanf("%s%s",a+1,b+1);
for(int i=n;i>=1;i--)
{
for(int j=m;j>=1;j--)
{
dp[i][j]=max(dp[i][j+1],dp[i+1][j]);
if(a[i]==b[j])dp[i][j]=dp[i+1][j+1]+1;
}
}
int ax=1,bx=1,len=n+m-dp[1][1];
for(int i=1;i<=len;i++)
{
if(ax>n)
{
for(;bx<=m;bx++)c[i++]=b[bx];
break;
}
if(bx>m)
{
for(;ax<=n;ax++)c[i++]=a[ax];
break;
}
if(a[ax]==b[bx])
{
c[i]=a[ax];
ax++,bx++;
}
else if(a[ax]<b[bx])
{
if((n-ax)+(m-bx+1)-dp[ax+1][bx]<=len-i)c[i]=a[ax++];
else c[i]=b[bx++];
}
else
{
if((n-ax+1)+(m-bx)-dp[ax][bx+1]<=len-i)c[i]=b[bx++];
else c[i]=a[ax++];
}
}
for(int i=1;i<=len;i++)
{
printf("%c",c[i]);
}
return 0;
}