Codeforces 10 D. LCIS

問題

n項からなる数列a[i]とm項からなる数列b[i]が与えられる。
これら二つの数列に共通する(必ずしも連続しない)部分列で、かつ単調増加になっているもののうち、長さ最大のものを求めよ。

制約条件

n,m≦500

方針

DP+経路復元。


dp[i][j]を、aをちょうどi番目まで、bをちょうどj番目まで使った時の増加列の長さの最大値とする。
これを愚直に更新すると、一回の更新にO(n^2)時間がかかり、
状態の数がO(n^2)あるため、全体でO(n^4)時間かかり間に合わない。


ここで、i,jおよび次に使うaの項a[k]を一つ決めると、bの項は
j番目以降で、a[k]に等しい項のうち最初に来るものとしていいことに注目する。


aiが、bのj番目以降で最初に来るときのインデックスをpos[i][j]として、
これを前処理で全部計算しておく。
するとDPの更新がO(n)で出来るようになり、全体でO(n^3)時間で増加列を求めることができる。

ソースコード

int n,m,a[500],b[500];
int pos[501][501]; // aiがbのj番目以降で最初に現れるときのインデックス
int dp[500][500],prev[500][500];

void run()
{
	cin>>n;
	rep(i,n)cin>>a[i];
	cin>>m;
	rep(i,m)cin>>b[i];
	
	memset(pos,-1,sizeof(pos));
	
	int ans=0,ai,bi;
	rep(i,n)rep(j,m){
		if(a[i]==b[j])ans=dp[i][j]=1, ai=i, bi=j;
		else dp[i][j]=-1;
	}
	
	rep(i,n){
		int p=-1;
		for(int j=m-1;j>=0;j--){
			if(a[i]==b[j])p=j;
			pos[i][j]=p;
		}
	}
	
	rep(i,n)rep(j,m)if(dp[i][j]>0)for(int k=i+1;k<n;k++)if(a[i]<a[k]){
		int l=pos[k][j+1];
		if(l>=0&&dp[k][l]<dp[i][j]+1){
			dp[k][l]=dp[i][j]+1; prev[k][l]=i*500+j;
			if(ans<dp[k][l])ans=dp[k][l], ai=k, bi=l;
		}
	}
	
	vi res;
	rep(i,ans){
		res.pb(a[ai]);
		int nai=prev[ai][bi]/500, nbi=prev[ai][bi]%500;
		ai=nai; bi=nbi;
	}
	reverse(all(res));
	cout<<ans<<endl;
	rep(i,ans)cout<<res[i]<<(i==ans-1?"\n":" ");
}