题目
给出一个由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;
}