bzoj3864-hdu4899-Hero meet devil

题目

给出一个由AGTC组成的字符串(S),长度为(n),对于每个(iin [0,n]),问有多少个长度为(m),仅含有AGTC的字符串(T)使得(S)(T)的最长公共子串长度为(i)((nle 15,mle 1000))

Sample Input

GTC
10

Sample Output

1
22783
528340
497452

分析

这是一个经典的问题,解法称为dp套dp。这一类问题要求解的是有多少种输入可以使得一个dp的最终结果为一个特定值。在这道题中的表现就是,求有多少个字符串(T)使得求解lcs的dp的最终结果为(i)。对于这类问题,一般的方法是,对于内部dp(这里的lcs)观察它的求解过程,把dp的过程状态压缩,放在外层dp中。

观察lcs的求解过程:

[lcs[i][j]=max egin{cases} lcs[i-1][j-1]+1 & ext{if t[i]=s[j]} \ lcs[i-1][j] \ lcs[i][j-1] end{cases} ]

注意到这个转移的过程中,一行只和上一行有关,而且同一行中相邻两位最多差1,所以我们可以把一行差分,状态压缩。即令(f[i][j])表示枚举到第(i)个字符,这时候内层dp状态为(j)的情况数。那么可以直接得到计算方程:

[egin{aligned} f[i][trans(j,k)]+=f[i-1][j] end{aligned} ]

其中(trans(j,k))表示从(j)状态加一个字母(k)转移到的状态。比如说,(s= ext{'ACT'},j=010),由于差分过,所以原来的状态是(011),即当前的匹配是C。接下来加入一个T,那么状态会变成(012),压缩后变为(011)。注意到这个trans是固定的,所以可以每次预处理。

预处理复杂度为(O(kn2^n)),外层dp复杂度为(O(km2^n))

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long giant;
const int maxn=15;
const int maxm=1<<maxn;
const int maxa=1e3+1;
const int q=1e9+7;
const char sta[]=" ACGT";
char s[maxn+1];
int t[maxm][5],f[maxa+2][maxm+2],ans[maxn+2],n,a[maxn+2];
int change(char c) {
	for (int i=1;i<=4;++i) if (c==sta[i]) return i;
	return 5;
}
void add(int &x,int y) {
	x+=y;
	if (x>q) x-=q;
}
int count(int x) {
	int ret=0;
	for (int i=0;i<maxn;++i) ret+=((x>>i)&1);
	return ret;
}
int tf[2][maxn+2];
int trans(int sit,int c) {
	memset(tf,0,sizeof tf);
	for (int i=0;i<n;++i) tf[0][i+1]=tf[0][i]+((sit>>i)&1);
	for (int i=1;i<=n;++i) {
		int tmp=0;
		if (a[i]==c) tmp=max(tmp,tf[0][i-1]+1);
		tmp=max(tmp,max(tf[0][i],tf[1][i-1]));
		tf[1][i]=tmp;
	}
	int ret=0;
	for (int i=0;i<n;++i) ret+=(1<<i)*(tf[1][i+1]-tf[1][i]);
	return ret;
}
int main() {
#ifndef ONLINE_JUDGE
	freopen("test.in","r",stdin);
	freopen("my.out","w",stdout);
#endif
	int T;
	scanf("%d",&T);
	while (T--) {
		memset(ans,0,sizeof ans);
		memset(f,0,sizeof f);
		memset(a,0,sizeof a);
		scanf("%s",s+1);
		n=strlen(s+1);
		int m;
		scanf("%d",&m);
		f[0][0]=1;
		for (int i=1;i<=n;++i) a[i]=change(s[i]);
		for (int j=0;j<(1<<n);++j) for (int k=1;k<=4;++k) t[j][k]=trans(j,k);
		for (int i=1;i<=m;++i) {
			for (int j=0;j<(1<<n);++j) {
				for (int k=1;k<=4;++k) {
					int s=t[j][k];
					add(f[i][s],f[i-1][j]);
				}
			}
		}
		for (int i=0;i<(1<<n);++i) add(ans[count(i)],f[m][i]);
		for (int i=0;i<=n;++i) printf("%d
",ans[i]);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/owenyu/p/6724616.html