CodeForces 10D LCIS
题目
题目大意
给定两个长度分别为 N,M N , M 的序列 A,B A , B ,求两个序列的最长公共上升子序列并输出方案。
思路
首先拿到这道题,我就决定将这道题分解为两个问题:最长上升子序列,最长公共子序列。
但是,交到CF上后,却~~成功地~~WA了。(QAQ。。。)
我开始了我的探索之旅。。。
Step1.时间复杂度为 O(N4) O ( N 4 ) 的暴力算法
可以仿造最长上升子序列的状态定义:定义 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≤j 1 ≤ i ≤ N , 1 ≤ j ≤ M , i < k ≤ i , j < l ≤ j
代码非常水,请读者自行实现。
Step2.时间复杂度为 O(N3) O ( N 3 ) 的算法
我们都知道,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≤M 1 ≤ i ≤ N , 1 ≤ j ≤ M 。
自然得出代码:(由于CF的评测机升级过一次,本题又是升级前的题,故 O(N3) O ( N 3 ) 的算法能够过)
#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 LOACL
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
scanf("%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 ( N 2 ) 的算法
还可以优化???
我们若要对它优化,也只有优化当 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 ] 的最大值。
我们不难发现: i i 的值是不变的,而的值是保持递增的,所以,我们可以考虑保存一个变量 t t ,记录一下在中,满足 A[i]>B[k] A [ i ] > B [ k ] 的最大值。当 j j 增加时,就可以只用的时间更新 t t 和。时间复杂度就成功的降到了 O(N2) O ( N 2 ) 。
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 LOACL
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
scanf("%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;
}
}