Description
Input
Output
Sample Input
GTC
10
Sample Output
22783
528340
497452
题意:给你一个串S,问所有长度为m的字符串中,与S串的最长公共子序列长度为1...|S|的串的个数。
题解:话说这种DP套DP的题最近有点流行~
还记得怎么求最长公共子序列吗?记得那个求最长公共子序列时的矩阵吗?不记得我就再说一遍。
令f[i][j]表示T串中到了第i个数,S串中到了第j个数,的LCS的长度。那么经典的DP方程:
$f[i][j]=max(f[i-1][j],f[i][j-1],(T[i]==S[j])?(f[i-1][j-1]+1):0)$
好了,但是我们求的是方案数,如果直接这样DP的话,需要记录的状态非常多(当前T可能的字符,之前T可能的字符。。。)。但是我们发现S的长度非常小,可以考虑把它单独拿出来处理一下。
因为每一行只能从上一行转移过来,我们不妨状压所有可能的行,暴力计算出在T中添加一个字符后会转移到哪个行。但是行中每一位的数不是0/1,差分一下就好了。处理出所有的转移后,再跑一个DP统计答案就行了。
#include <cstdio> #include <cstring> #include <iostream> #include <cstring> using namespace std; const int mod=1000000007; int n,m; int to[1<<16][4],s[20],cnt[1<<16],f[2][1<<16],ans[20]; char str[20]; void init() { memset(to,0,sizeof(to)); memset(cnt,0,sizeof(cnt)); memset(f,0,sizeof(f)); memset(ans,0,sizeof(ans)); int i,j,k,s1,s2,t1,t2,tar; for(i=0;i<(1<<n);i++) { if(i) cnt[i]=cnt[i-(i&-i)]+1; for(j=0;j<4;j++) { for(tar=s1=s2=t1=t2=0,k=0;k<n;k++) { t1=s1,t2=s2,s2+=((i>>k)&1),s1=max(t1,s2); if(s[k]==j) s1=max(s1,t2+1); tar|=((s1-t1)<<k); } to[i][j]=tar; } } } void work() { scanf("%s%d",str,&m),n=strlen(str); int i,j; for(i=0;i<n;i++) { if(str[i]=='A') s[i]=0; if(str[i]=='G') s[i]=1; if(str[i]=='C') s[i]=2; if(str[i]=='T') s[i]=3; } init(); f[0][0]=1; for(j=0;j<=m;j++) { for(i=0;i<(1<<n);i++) f[(j&1)^1][i]=0; for(i=0;i<(1<<n);i++) f[(j&1^1)][to[i][0]]=(f[j&1^1][to[i][0]]+f[j&1][i])%mod,f[(j&1^1)][to[i][1]]=(f[j&1^1][to[i][1]]+f[j&1][i])%mod,f[(j&1^1)][to[i][2]]=(f[j&1^1][to[i][2]]+f[j&1][i])%mod,f[(j&1^1)][to[i][3]]=(f[j&1^1][to[i][3]]+f[j&1][i])%mod; } for(i=0;i<(1<<n);i++) ans[cnt[i]]=(ans[cnt[i]]+f[m&1][i])%mod; for(i=0;i<=n;i++) printf("%d\n",ans[i]); } int main() { int T; scanf("%d",&T); while(T--) work(); return 0; }
| 欢迎来原网站坐坐! >原文链接<