【BZOJ4275】[ONTAK2015]Badania naukowe DP

【BZOJ4275】[ONTAK2015]Badania naukowe

Description

给定三个数字串A,B,C,请找到一个A,B的最长公共子序列,满足C是该子序列的子串。

Input

第一行包含一个正整数n(1<=n<=3000),表示A串的长度。
第二行包含n个正整数,其中第i个数表示A[i](1<=A[i]<=1000)。
第三行包含一个正整数m(1<=m<=3000),表示B串的长度。
第四行包含m个正整数,其中第i个数表示B[i](1<=B[i]<=1000)。
第五行包含一个整数k(0<=k<=3000),表示C串的长度。
第六行包含k个正整数,其中第i个数表示C[i](1<=C[i]<=1000)。

Output

输出一个整数,即满足条件的最长公共子序列的长度,如果无解输出-1。特别的,如果k为0且无解,请输出0。

Sample Input

7
1 2 2 3 1 1 2
6
1 2 1 3 1 2
2
3 2

Sample Output

4

HINT

找到的最长个公共子序列为(1,2,3,2)。

题解:我们先对于A和B的每个位置,处理出所有以i为结尾的,与C相同的子序列中,起始点最靠右的起始点位置pa[i]和pb[i]。然后处理出A和B的前缀最长公共子序列和后缀最长公共子序列f和g。用f[pa[i]-1][pb[j]-1]+g[i+1][j+1]更新答案即可。

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int A,B,C,ans;
int a[3010],b[3010],c[3010];
int f[3010][3010],g[3010][3010],pa[3010],pb[3010];
inline int rd()
{
	int ret=0,f=1;	char gc=getchar();
	while(gc<'0'||gc>'9')	{if(gc=='-')	f=-f;	gc=getchar();}
	while(gc>='0'&&gc<='9')	ret=ret*10+gc-'0',gc=getchar();
	return ret*f;
}
int main()
{
	int i,j,k;
	for(A=rd(),i=1;i<=A;i++)	a[i]=rd();
	for(B=rd(),i=1;i<=B;i++)	b[i]=rd();
	for(C=rd(),i=1;i<=C;i++)	c[i]=rd();
	for(i=1;i<=A;i++)	for(j=1;j<=B;j++)
	{
		f[i][j]=max(f[i][j-1],f[i-1][j]);
		if(a[i]==b[j])	f[i][j]=max(f[i][j],f[i-1][j-1]+1);
	}
	if(!C)
	{
		printf("%d",f[A][B]);
		return 0;
	}
	for(i=A;i>=1;i--)	for(j=B;j>=1;j--)
	{
		g[i][j]=max(g[i][j+1],g[i+1][j]);
		if(a[i]==b[j])	g[i][j]=max(g[i][j],g[i+1][j+1]+1);
	}
	for(i=1;i<=A;i++)
	{
		for(k=i,j=C;k>=1;k--)
		{
			if(a[k]==c[j])	j--;
			if(!j)	break;
		}
		pa[i]=k-1;
	}
	for(i=1;i<=B;i++)
	{
		for(k=i,j=C;k>=1;k--)
		{
			if(b[k]==c[j])	j--;
			if(!j)	break;
		}
		pb[i]=k-1;
	}
	for(i=1;i<=A;i++)	for(j=1;j<=B;j++)	if(pa[i]!=-1&&pb[j]!=-1)	ans=max(ans,f[pa[i]][pb[j]]+g[i+1][j+1]+C);
	if(!ans)	printf("-1");
	else	printf("%d",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/CQzhangyu/p/7749406.html