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;
}