CodeForces 10D LCIS
题目
◇题目传送门◆
题目大意
给定两个长度分别为N,MN,M的序列A,BA,B,求两个序列的最长公共上升子序列并输出方案。
思路
首先拿到这道题,我就决定将这道题分解为两个问题:最长上升子序列,最长公共子序列。
但是,交到CF上后,却~~成功地~~WA了。(QAQ。。。)
我开始了我的探索之旅。。。
Step1.时间复杂度为O(N4)O(N4)的暴力算法
可以仿造最长上升子序列的状态定义:定义f[i][j]f[i][j]为以A[i],B[j]A[i],B[j]为结尾的最长公共上升子序列。
不难列出状态转移方程
其中1≤i≤N,1≤j≤M,i<k≤i,j<l≤j1≤i≤N,1≤j≤M,i<k≤i,j<l≤j
代码非常水,请读者自行实现。
Step2.时间复杂度为O(N3)O(N3)的算法
我们都知道,LCIS是由LCS和LIS演变而来,所以,我们分析一下LCS和LIS的算法:
在LIS中,f[i]f[i]表示以A[i]A[i]结尾的最长上升子序列的长度,LCS中f[i][j]f[i][j]表示以A[1…i],B[1…j]A[1…i],B[1…j]的最长公共子序列的长度。
为什么LIS要这么定义???
因为我们在计算f[i]f[i]时,需要考虑上一个值,这样才能确定是否将A[i]A[i]加入,而在LCS中,当我们计算f[i][j]f[i][j]时,是不需要考虑上一个值的,只需考虑当前值即可。
而在LCIS中,我们也需要知道上一个值的信息,f[i][j]f[i][j],也就是上一个值的信息为A[i],B[j]A[i],B[j],而当A[i]=B[j]A[i]=B[j]时,信息是重复的。所以,我们要消除这种重复。
综上所述,我们改变一下状态定义:
定义f[i][j]f[i][j]为在A[1…i],B[1…j]A[1…i],B[1…j]上的LCIS,并在B[j]B[j]处结尾。
则得出状态转移方程:
其中:1≤i≤N,1≤j≤M1≤i≤N,1≤j≤M。
自然得出代码:(由于CF的评测机升级过一次,本题又是升级前的题,故O(N3)O(N3)的算法能够过)
#include<cstdio>
#include<algorithm>
using namespace std;const int Maxn=500;int N,M;
int A[Maxn+5],B[Maxn+5];
int f[Maxn+5][Maxn+5];int main() {#ifdef LOACLfreopen("in.txt","r",stdin);freopen("out.txt","w",stdout);#endifscanf("%d",&N);for(int i=1;i<=N;i++)scanf("%d",&A[i]);scanf("%d",&M);for(int i=1;i<=M;i++)scanf("%d",&B[i]);int ans=0;for(int i=1;i<=N;i++)for(int j=1;j<=M;j++) {if(A[i]!=B[j])f[i][j]=f[i-1][j];else {int tmp=0;for(int k=1;k<j;k++)if(f[i-1][k]>tmp&&B[j]>B[k])tmp=f[i-1][k];f[i][j]=tmp+1;if(ans<f[i][j])ans=f[i][j];}}printf("%d\n",ans);return 0;
}
Step3.时间复杂度为O(N2)O(N2)的算法
还可以优化???
我们若要对它优化,也只有优化当A[i]=B[j]A[i]=B[j]的状态转移了。因为外层的两个循环,是无论如何也搞不掉的。
看看这段代码:
int tmp=0;
for(int k=1;k<j;k++)if(f[i-1][k]>tmp&&B[j]>B[k])tmp=f[i-1][k];
f[i][j]=tmp+1;
当A[i]=B[j]A[i]=B[j]时,就等价于:
int tmp=0;
for(int k=1;k<j;k++)if(f[i-1][k]>tmp&&A[i]>B[k])tmp=f[i-1][k];
f[i][j]=tmp+1;
这是在求在f[i?1][k](1≤k<j)f[i?1][k](1≤k<j)中,满足A[i]>B[k]A[i]>B[k]的最大值。
我们不难发现:ii的值是不变的,而 的值是保持递增的,所以,我们可以考虑保存一个变量tt,记录一下在 中,满足A[i]>B[k]A[i]>B[k]的最大值。当jj增加时,就可以只用 的时间更新tt和 。时间复杂度就成功的降到了O(N2)O(N2)。
Step4.空间复杂度为O(N)O(N)的算法
我们甚至可以采用滚动数组来进行DP。
但我们在输出路径时就不能使用递归输出,而应多开一个数组来记录路径。
正解代码
因为要输出路径,所以我们需要开一个pre
数组来记录状态转移的路径。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int Maxn=500;
int N,M;
int A[Maxn+5],B[Maxn+5];
int f[Maxn+5],pre[Maxn+5];
int ans[Maxn+5];
int main() {#ifdef LOACLfreopen("in.txt","r",stdin);freopen("out.txt","w",stdout);#endifscanf("%d",&N);for(int i=1;i<=N;i++)scanf("%d",&A[i]);scanf("%d",&M);for(int i=1;i<=M;i++)scanf("%d",&B[i]);for(int i=1;i<=N;i++) {int pos=0;for(int j=1;j<=M;j++) {if(A[i]==B[j])f[j]=f[pos]+1,pre[j]=pos;if(A[i]>B[j]&&f[j]>f[pos])pos=j;}}int maxx=-1,pos;for(int i=1;i<=M;i++)if(f[i]>maxx) {maxx=f[i];pos=i;}for(int i=maxx;i>=0;i--) {ans[i]=B[pos];pos=pre[pos];}printf("%d\n",maxx);for(int i=1;i<=maxx;i++)printf("%d ",ans[i]);return 0;
}
如果你采用的二维数组,也可以采用递归输出:
void Print(int x,int y) {if(f[x][y]==1) {printf("%d ",B[y]);return;}if(x==0||y==0)return;if(f[x][y]==f[x-1][y]) {Print(x-1,y);return;}for(int i=y-1;i>=1;i--)if(B[i]<B[y]&&f[x][y]==f[x-1][i]+1) {Print(x,i);printf("%d ",B[y]);return;}
}