常见子序列DP模型
最长不下降(上升)子序列(LIS)
记 \(f_{len}\) 为长度为 \(len\) 的最长不下降子序列的最后一个数的最小值。
显然 \(f\) 单调不降。
记 \(cnt\) 为当前 \(f\) 更新到的最大长度。
正向枚举原数列 \(a\),如果 \(a_i \ge f_{cnt}\),表明可以继续把最长的子序列再增长一个长度,即 \(f_{++cnt}=a_i\)。而对于长度比 \(cnt\) 更短的 \(f\) 值,\(a_i\) 显然更新不了它们。因为 \(a_i\) 显然比他们大,显然我们希望最后一个数尽可能小,从而可以往后加更多的数。
如果 \(a_i<f_{cnt}\),则二分找出一个 \(f_j\) 使得其长度尽可能大并小于等于 \(a_i\),然后把 \(f_j\) 改成 \(a_i\)。显然 \(a_i\) 只能更新这一个 \(f\)。
最后答案就是 \(cnt\)。
时间复杂度 \(O(n \log n)\)。
//LIS
memset(f,0x3f,sizeof(f));
f[1]=a[1];
cnt=1;
for(i=2;i<=n;i++){
l=1;r=cnt+1;
while(l<r){
mid=(l+r)/2;
if(f[mid]>a[i])
r=mid;
else
l=mid+1;
}
f[l]=a[i];
if(l==cnt+1) cnt++;
}
cout<<cnt<<endl;
最长公共子序列(LCS)
记 \(f_{i,j}\) 表示 \(a_1,a_2,...,a_{i}\) 与 \(b_1,b_2,...,b_j\) 的最长公共子序列长度。
有如下转移方程:
时间复杂度 \(O(n^2)\)。
n=s.size(); m=t.size();
for(i=1;i<=n;i++){
for(j=1;j<=m;j++){
if(s[i-1]==t[j-1]){
f[i][j]=f[i-1][j-1]+1;
}
else{
f[i][j]=max(f[i][j-1],f[i-1][j]);
}
}
}
cout<<f[n][m]<<endl;
如果给的 \(a\) 和 \(b\) 是排列(P1439),那么有 \(O(n \log n)\) 的做法:挼行止爷爷的题解。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const long long N=1e5+1;
long long n,a[N+10],tr[N+10],f[N+10],cnt;
int main(){
long long i,j,u,v;
scanf("%lld",&n);
for(i=1;i<=n;i++)
scanf("%lld",&a[i]);
for(i=1;i<=n;i++){
scanf("%lld",&u);
tr[u]=i;
}
for(i=1;i<=n;i++) a[i]=tr[a[i]];
f[1]=a[1];cnt=1;
long long l,r,mid;
for(i=2;i<=n;i++){
l=1,r=cnt+1;
while(l<r){
mid=l+r>>1;
if(f[mid]>a[i]) r=mid;
else l=mid+1;
}
f[l]=a[i];
if(l>cnt) cnt++;
}
printf("%lld\n",cnt);
return 0;
}
最长公共上升子序列(LCIS)
设 \(f_{i,j}\) 表示在 \(a\) 的前 \(i\) 个数,\(b\) 的前 \(j\) 个数,且以 \(b_j\) 结尾的 LCIS 长度。
有转移 \(\begin{cases} f_{i,j}=f_{i-1,j}\\f_{i,j}=\max{f_{i-1,k}}+1,a_i=b_j \end{cases}\)
发现后面那个转移可以记一下。于是就 \(O(n^2)\) 做完了。
输出方案的话记录一下前面更新的是哪一个就好了。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline long long read(){char ch=getchar();long long x=0,f=1;while(ch<'0' || ch>'9'){if(ch=='-') f=-1;ch=getchar();}
while('0'<=ch && ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;}
inline void write(long long x){if(x<0){putchar('-');x=-x;}if(x>9) write(x/10);putchar(x%10+'0');}
const long long N=501;
long long n,a[N+10],m,b[N+10],f[N+10][N+10],ans,mx,id,pre[N+10][N+10],s[N+10],tot;
int main(){
long long i,j,u,v;
n=read();for(i=1;i<=n;i++) a[i]=read();
m=read();for(i=1;i<=m;i++) b[i]=read();
for(i=1;i<=n;i++){
mx=0,id=0;
for(j=1;j<=m;j++){
pre[i][j]=pre[i-1][j];
f[i][j]=f[i-1][j];
if(a[i]==b[j]){
if(f[i][j]<mx+1){
f[i][j]=mx+1;
pre[i][j]=id;
}
}
else if(a[i]>b[j]){
if(f[i-1][j]>mx){
mx=f[i-1][j];
id=j;
}
}
}
}
id=0;
for(i=1;i<=m;i++){
if(ans<f[n][i]){
ans=f[n][i];
id=i;
}
}
write(ans),putchar('\n');
for(;id;id=pre[n][id]){
s[++tot]=b[id];
}
while(tot){
write(s[tot--]),putchar(' ');
}
putchar('\n');
return 0;
}