bzoj3864-hdu4899-Hero meet devil

题目

给出一个由AGTC组成的字符串\(S\),长度为\(n\),对于每个\(i\in [0,n]\),问有多少个长度为\(m\),仅含有AGTC的字符串\(T\)使得\(S\)\(T\)的最长公共子串长度为\(i\)\((n\le 15,m\le 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 \begin{cases} lcs[i-1][j-1]+1 & \text{if t[i]=s[j]} \\ lcs[i-1][j] \\ lcs[i][j-1] \end{cases} \]

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

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

其中\(trans(j,k)\)表示从\(j\)状态加一个字母\(k\)转移到的状态。比如说,\(s=\text{'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\n",ans[i]);
	}
	return 0;
}
posted @ 2017-04-17 20:18  permui  阅读(723)  评论(0编辑  收藏  举报