CXXXIV.[BZOJ3864]Hero meet devil
我们不妨从最trival的LCS问题上想起:暴力的LCS求法是什么?
设 \(f(i,j)\) 表示一个串(不妨设为本题中要填的字符串 \(T\))的前 \(i\) 位与另一个串(即题目中给出的 \(S\))的前 \(j\) 位所构成的串的LCS。则 \(f(i,j)=\max\Big\{f(i,j-1),f(i-1,j),f(i-1,j-1)+[t_i=s_j]\Big\}\)。
因为 \(|S|\) 很小,所以我们可以考虑设 \(f(i,\mathbb{S})\) 表示当前填到 \(T\) 的第 \(i\) 位,且 \(\mathbb{S}\) 中通过某种方式储存了 \(f(i,0)\sim f(i,|S|)\) 全部的DP值,满足此种情形的串的方案数。下面考虑怎么搞出 \(\mathbb{S}\)。
明显,对于 \(f(i,0\sim|S|)\) 来说,相邻两个数的差至多为 \(1\),这一点可以直接从DP式上看出。于是我们可以状压其差分数组,即可实现由数组到状态的一一映射。这之后,我们只需预处理出对于每个 \(\mathbb S\) 对应的状态,其之后添加 A,G,C,T
四个字符会分别到达哪个新状态即可。然后直接DP就行了。
这种手法被称作“DP on DP”,因为DP状态中储存了另一种更简单的DP的数组。可以发现,简单DP的数组压缩后得到了一张自动姬。
时间复杂度 \(O(m2^n)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int T,n,m,f[2][1<<15],lim,t[20],a[20],b[20],res[20],trans[1<<15][4];
char s[20];
int main(){
scanf("%d",&T);
while(T--){
scanf("%s%d",s+1,&m),n=strlen(s+1),lim=1<<n;
for(int i=1;i<=n;i++){
if(s[i]=='A')t[i]=0;
if(s[i]=='G')t[i]=1;
if(s[i]=='C')t[i]=2;
if(s[i]=='T')t[i]=3;
}
for(int j=0;j<lim;j++){
for(int k=0;k<n;k++)a[k+1]=a[k]+((j>>k)&1);
for(int c=0;c<4;c++){
for(int k=1;k<=n;k++)b[k]=max({a[k-1]+(t[k]==c),a[k],b[k-1]});
trans[j][c]=0;
for(int k=0;k<n;k++)trans[j][c]|=(b[k+1]-b[k])<<k;
}
}
memset(f,0,sizeof(f)),f[0][0]=1;
for(int i=0;i<m;i++){
memset(f[!(i&1)],0,sizeof(f[!(i&1)]));
for(int j=0;j<lim;j++)for(int k=0;k<4;k++)(f[!(i&1)][trans[j][k]]+=f[i&1][j])%=mod;
}
// for(int i=0;i<=m;i++){for(int j=0;j<lim;j++)printf("%d ",f[i][j]);puts("");}
for(int i=0;i<lim;i++)(res[__builtin_popcount(i)]+=f[m&1][i])%=mod;
for(int i=0;i<=n;i++)printf("%d\n",res[i]),res[i]=0;
}
return 0;
}