Description
给出一个字符串S,这个字符串只由,ACGT四个字母组成。对于每个1一|S|中的每一个i.求出满足以下条件的字符串T的个数:
- 长度为m
- 只由ACGT组成
- lcs为i
Solution
第一步考虑已知两个串,计算两串之间的lcs,i表示A串指针,j表示B串指针
$$f[i][j]=max(f[i-1][j],f[i][j-1])$$
$$if(s[i]==t[j]) f[i][j]=max(f[i-1][j-1]+1)$$
由于s串比较短,把f数组用差分压缩成二进制状态 第二步是外层DP,$dp[i][j]$表示,已经生成了i位的字符串,此时lcs为j的字符串个数;$tr[j][k]$表示j状态下增加字符k得到的状态
$$dp[i+1][tr[j][k]]+=dp[i][j]$$
DP套DP求解
#include<iostream> #include<cstring> #include<cstdio> #include<cmath> using namespace std; int T,n,m,a[20],tran[100000][4],f[2][20],dp[1005][40000],ans[20]; const int mod=1000000007; char s[20]; inline int read() { int f=1,w=0; char ch=0; while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { w=(w<<1)+(w<<3)+ch-'0'; ch=getchar(); } return f*w; } int change(char x) { if(x=='A') return 1; if(x=='C') return 2; if(x=='G') return 3; return 4; } int trans(int v,int c) { memset(f,0,sizeof(f)); for(int i=0;i<n;i++) f[0][i+1]=f[0][i]+((v>>i)&1); for(int i=1;i<=n;i++) { f[1][i]=max(f[0][i],f[1][i-1]); if(a[i]==c) f[1][i]=max(f[1][i],f[0][i-1]+1); } int ret=0; for(int i=0;i<n;i++) if(f[1][i+1]-f[1][i]) ret|=(1<<i); return ret; } int cnt(int x) { int ret=0; while(x) { if(x&1) ret++; x>>=1; } return ret; } int main() { T=read(); for(;T;T--) { memset(dp,0,sizeof(dp)); memset(ans,0,sizeof(ans)); scanf("%s",s+1); m=read(); n=strlen(s+1); for(int i=1;i<=n;i++) a[i]=change(s[i]); for(int i=0;i<(1<<n);i++) for(int j=1;j<=4;j++) tran[i][j]=trans(i,j); dp[0][0]=1; for(int i=1;i<=m;i++) for(int j=0;j<(1<<n);j++) for(int k=1;k<=4;k++) { int temp=tran[j][k]; (dp[i][temp]+=dp[i-1][j])%=mod; } for(int i=0;i<(1<<n);i++) (ans[cnt(i)]+=dp[m][i])%=mod; for(int i=0;i<=n;i++) printf("%d ",ans[i]); } return 0; }