当前位置: 代码迷 >> 综合 >> 【CodeForces】【DP】10D LCIS
  详细解决方案

【CodeForces】【DP】10D LCIS

热度:29   发布时间:2023-11-21 07:10:26.0

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]为结尾的最长公共上升子序列。

不难列出状态转移方程

f[i][j]={ max(f[k][l])+10A[i]=B[j],A[i]>A[k]A[i]B[j]f[i][j]={max(f[k][l])+1A[i]=B[j],A[i]>A[k]0A[i]≠B[j]

其中1iN,1jM,i<ki,j<lj1≤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[1i],B[1j]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[1i],B[1j]A[1…i],B[1…j]上的LCIS,并在B[j]B[j]处结尾。

则得出状态转移方程:

f[i][j]={ f[i?1][j]max(f[i?1][k])A[i]B[j]1k<j,B[j]>B[k]f[i][j]={f[i?1][j]A[i]≠B[j]max(f[i?1][k])1≤k<j,B[j]>B[k]

其中:1iN,1jM1≤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](1k<j)f[i?1][k](1≤k<j)中,满足A[i]>B[k]A[i]>B[k]的最大值。

我们不难发现:ii的值是不变的,而 j 的值是保持递增的,所以,我们可以考虑保存一个变量tt,记录一下在 f [ i ? 1 ] [ k ] ( 1 k < j ) 中,满足A[i]>B[k]A[i]>B[k]的最大值。当jj增加时,就可以只用 O ( 1 ) 的时间更新tt f [ i ] [ j ] 。时间复杂度就成功的降到了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;}
}