考虑裸的最长公共子序列。
int n,m,g[20][20];
char strA[20],strB[20];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
g[i][j]=max(g[i-1][j],g[i][j-1]);
g[i][j]=max(g[i][j],g[i-1][j-1]+(strA[i]==strB[j]));
}
然后大家可能都会做了,就没了,嗯。
我们可以求满足一种性质的字符串的个数,这种性质可以用 (g[i][j]) 描述。
设 (f[i][一个奇怪的东西]) 表示 (g[i][j],1le jle n) 的值为 那个奇怪的东西 的字符串的个数。
这个 那个奇怪的东西 不好表示,它是一个数组,需要像个办法将它压下来。
仔细想一想会发现,(g[i][j-1]le g[i][j]le g[i][j-1]+1) 。会发现 (g[i][j]) 的差分数组只有 0 和 1。那么我们可以用一个二进制数存储 这个奇怪的东西。
那么我们设 (f[i][j]) 表示长度为 (i) 的字符串,和 (S) 的 (g[i][j]) 数组的差分数组为 (j) 的个数。
我们可以枚举 (i,j) ,枚举后面接的字符转移,转移的时候先将 二进制数 解密成 (g[i][j]),再仿造 最长公共子序列 的方法更新 (g[i][j]),最后再将 (g[i][j]) 加密成 二进制数 就行了。
#include<bits/stdc++.h>
using namespace std;
const int mod=1000000007;
int Len,m,End,a[20],ans[20];
int sum[20],g[20],cnt1[33000];
int f[1003][33000],to[33000][5];
char str[20];
inline int read()
{
int x=0,w=0;char ch=0;
while(!isdigit(ch)){w|=ch=='-';ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return w?-x:x;
}
int calc(int state,int ch)
{
int temp=0;
sum[0]=0;
for(int i=0;i<Len;i++)
sum[i+1]=sum[i]+(state>>i&1);
memset(g,0,sizeof g);
for(int i=1;i<=Len;i++){
if(a[i]==ch)g[i]=sum[i-1]+1;
g[i]=max(g[i],max(sum[i],g[i-1]));
}
for(int i=0;i<Len;i++)
temp+=(g[i+1]-g[i])<<i;
return temp;
}
int main()
{
int T=read();
for(int i=1;i<=32767;i++)
cnt1[i]=cnt1[i>>1]+(i&1);
while(T --> 0){
scanf("%s",str+1);
memset(f,0,sizeof f);
memset(ans,0,sizeof ans);
End=1<<(Len=strlen(str+1));
m=read();
for(int i=1;i<=Len;i++)
if(str[i]=='A')a[i]=1;
else if(str[i]=='G')a[i]=2;
else if(str[i]=='C')a[i]=3;
else a[i]=4;
f[0][0]=1;
for(int i=0;i<End;i++)
for(int j=1;j<=4;j++)
to[i][j]=calc(i,j);
for(int l=0;l<m;l++)
for(int i=0;i<End;i++)
for(int c=1;c<=4;c++)
(f[l+1][to[i][c]]+=f[l][i])%=mod;
for(int i=0;i<End;i++)
(ans[cnt1[i]]+=f[m][i])%=mod;
for(int i=0;i<=Len;i++)
printf("%d
",ans[i]);
}
}