https://vjudge.net/problem/CodeForces-10D
题目
给两个序列,求他们的最长上升公共子序列。每个序列的长度小于等于500。
题解
设$dp[i][j]$为以$j$为结尾的最长上升公共子序列的长度,那么
当$a[i]==b[j]$时$displaystyle dp[i][j]=(max_{p<i,q<j,b[q]<b[j]} dp[p][q])+1$
当$a[i]!=b[j]$时$displaystyle dp[i][j]=max_{p<i} dp[p][j]$
这样时间复杂度是$O(n^4)$
可以通过数学归纳法,优化重复计算的max
先优化不等的部分,根据贪心可以知道p小答案不会更优,那么$dp[i][j]=dp[i-1][j]$
然后优化等于的部分,根据贪心可以知道p小答案不会更优,那么$displaystyle dp[i][j]=(max_{q<j,b[q]<b[j]} dp[i-1][q])+1$
然后在循环的时候,已经循环过一次j,可以在循环的时候记录$displaystylemax_{q<j,b[q]<b[j]} dp[i-1][q]$
为了输出结果,还需要记录每个位置的转移,然后倒过来输出就可以了(答案为0时不输出)
#include<cstdio> #include<cstring> #include<algorithm> #define REP(i,a,b) for(register int i=(a); i<(b); i++) #define REPE(i,a,b) for(register int i=(a); i<=(b); i++) #define PERE(i,a,b) for(register int i=(a); i>=(b); i--) using namespace std; typedef long long ll; int dp[507][507], ch[507][507]; int a[507], b[507]; int n,m; inline void go(int x) { if(~ch[n-1][x]) go(ch[n-1][x]); printf("%d ", b[x]); } int main() { scanf("%d", &n); REP(i,0,n) { scanf("%d", &a[i]); } scanf("%d", &m); REP(i,0,m) { scanf("%d", &b[i]); } memset(dp,0,sizeof dp); memset(ch,-1,sizeof ch); REP(i,0,n) { int k=-1, v=0; REP(j,0,m) { if(a[i]==b[j]) { //dp[i][j] = max(dp[i-1][1..j-1])+1 [b[k]<a[i]] dp[i][j] = v+1; ch[i][j] = k; } else { if(i) dp[i][j] = dp[i-1][j], ch[i][j]=ch[i-1][j]; } if(i && b[j]<a[i]) { if(v<dp[i-1][j]) { v=dp[i-1][j]; k=j; } } } } int ans=0, x=-1; REP(j,0,m) if(ans<dp[n-1][j]) { ans = dp[n-1][j]; x = j; } printf("%d ", ans); if(ans) { if(~ch[n-1][x]) go(ch[n-1][x]); printf("%d", b[x]); } putchar(' '); }