题解
首先我们考虑如果我们知道了原串,那我们要求两个串的 $ ext{Lcs}$ 的长度就是 $ ext{dp}$ : $f[i][j]$ 表示 $S$ 串到 $i$ , $T$ 串到 $j$ 的 $ ext{Lcs}$ 长度,考虑转移: $f[i][j]=max(f[i-1][j],f[i][j-1],f[i-1][j-1]+[S[i]==T[j]])$ ,我们可以发现 $f[i][j]-f[i][j-1]in {0,1}$ 。回到原题,我们可以做 $ ext{dp}$ : $F[i][S][0/1/2]$ 表示已经放完了前 $i$ 个位置,如果把前面的位置和匹配串拿去做 $f$ 的 $ ext{dp}$ 的话, $f[i]$ 的差分数组组成的状态为 $S$ ,最后 $0/1/2$ 个字母是和 $NOI$ 的前几个字母是一样的的方案数。在此之前我们可以预处理出 $g[S][c]$ 表示目前差分数组的状态为 $S$ ,放入 $c$ 后新的差分数组的状态。考虑转移的话就枚举下一个位置放什么字母即可。效率: $O(n2^k)$ 。
考虑从假设我们已经知道了题目中的某个信息,如何求答案入手,可能可以得到正解。
代码
#include <bits/stdc++.h> using namespace std; const int N=1<<15,P=1e9+7; int n,k,m,g[N][3],a[20],b[20],X,Y=1,t[N],s[20],f[2][N][3]; char h[20],d[5]="NOI"; int get(int s,int c){ for (int i=1;i<=k;i++) a[i]=a[i-1]+(s&1),s>>=1; for (int i=1;i<=k;i++) b[i]=max(max(b[i-1],a[i]), a[i-1]+(d[c]==h[i])); for (int i=k;i;i--) s<<=1,s|=(b[i]-b[i-1]); return s; } int main(){ scanf("%d%d%s",&n,&k,h+1); m=1<<k;f[0][0][0]=1; for (int i=0;i<m;i++) for (int c=0;c<3;c++) g[i][c]=get(i,c); for (int i=1;i<=n;i++){ memset(f[Y],0,sizeof f[Y]); for (int j=0;j<m;j++) for (int k=0,o;k<3;k++) for (int c=0;c<3;c++){ if (!c) o=1; else if (c<2) o=k==1?2:0; else o=k==2?3:0; if (o==3) continue; (f[Y][g[j][c]][o]+=f[X][j][k])%=P; } X^=1,Y^=1; } for (int i=0;i<m;i++){ t[i]=t[i>>1]+(i&1); for (int j=0;j<3;j++) (s[t[i]]+=f[X][i][j])%=P; } for (int i=0;i<=k;i++) printf("%d ",s[i]); return 0; }