小豆参加了NOI的游园会,会场上每完成一个项目就会获得一个奖章,奖章只会是 \(\mathrm N,\mathrm O,\mathrm I\) 的字样。在会场。上他收集到了 \(K\) 个奖章组成的串。兑奖规则是奖章串和兑奖串的最长公共子序列长度为小豆最后奖励的等级。现在已知兑奖串长度为 \(N\) ,并且在兑奖串上不会出现连续三个奖章为 \(\mathrm N\mathrm O\mathrm I\) ,即奖章中不会出现子串\(\mathrm N\mathrm O\mathrm I\) 。现在小豆想知道各个奖励等级会对应多少个不同的合法兑奖串。对 \(10^9+7\) 取模 .

\(1\leq n\leq 1000,k\leq 15\)

\(dp\)\(dp\) 题 .

看到这个 \(k\) ,想到状压缩 \(dp\) . 到那时又无从下手,这个最长公共子序列的长度怎么考虑 .

首先,最长公共子序列怎么求,也是一个 \(dp\) 问题 .

\(f(i,j)\) 表示第一个串考虑到第 \(i\) 位,第二个串考虑到第 \(j\) 位的最长公共子序列长度 .

有三种转移,

\(f(i,j)\rightarrow f(i+1,j)\)

\(f(i,j)\rightarrow f(i,j+1)\)

\(if\ a_i=b_j,\ f(i,j)\rightarrow f(i+1,j+1)+1\)

考虑对于一个特定的 \(i\)\(f(i,j)=f(i,j-1)\) 或者 \(f(i,j)=f(i,j-1)+1\) .

此时,就可以状压了,状压 \(f(i)\) 的差分数组 .

\(g(i,s,0/1/2)\) 表示到第 \(i\) 位,差分数组为 \(s\)\(\mathrm N,\mathrm O,\mathrm I\) 连续出现了 \(0/1/2\) 个的方案数 .

可以预处理 \(a(s,0/1/2)\) 表示,差分数组为 \(s\) 时加入 \(\mathrm N/\mathrm O/\mathrm I\) 的新的差分数组状压 .

因此,就可以对应转移了 .

在预处理 \(a\) 之后 \(dp\) 转移就很简单了,但是要注意一些细节 .

时间复杂度 : \(\mathrm O(n2^k+k^22^k)\)

空间复杂度 : \(\mathrm O(k^2)\)

code

#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int n,k;
char s[20];
int a[20],b[20];
int g[1<<16][3],to[3][3];
int f[2][1<<16][3];
int ans[20];
void work_a(int mask){
	memset(a,0,sizeof(a));
	a[0]=mask&1;
	for(int i=1;i<k;i++){
		a[i]=a[i-1];
		if(mask&(1<<i))a[i]++;
	}
}
void work_b(int c){
	memset(b,0,sizeof(b));
	b[0]=max(0,a[0]);
	if((s[0]=='N'&&c==0)||(s[0]=='O'&&c==1)||(s[0]=='I'&&c==2)){
		b[0]=max(1,a[0]);
	}
	for(int i=1;i<k;i++){
		b[i]=max(b[i-1],a[i]);
		if((s[i]=='N'&&c==0)||(s[i]=='O'&&c==1)||(s[i]=='I'&&c==2))
			b[i]=max(b[i],a[i-1]+1);
	}
}
inline void upd(int &x,int y){x=(x+y)%mod;}
int main(){
	scanf("%d%d%s",&n,&k,s);
	for(int mask=0;mask<(1<<k);mask++){
		work_a(mask);
		for(int i=0;i<3;i++){
			work_b(i);
			int s=0;
			if(b[0])s|=1;
			for(int j=1;j<k;j++){
				if(b[j]-b[j-1]==1)s|=1<<j;
			}
			g[mask][i]=s;
		}
	}
	to[0][0]=1;to[0][1]=0;to[0][2]=0;
	to[1][0]=1;to[1][1]=2;to[1][2]=0;
	to[2][0]=1;to[2][1]=0;to[2][2]=3;
	upd(f[0][g[0][0]][1],1);
	upd(f[0][g[0][1]][0],1);
	upd(f[0][g[0][2]][0],1);
	for(int i=0;i+1<n;i++){
		int cur=i&1,nxt=cur^1;
		memset(f[nxt],0,sizeof(f[nxt]));
		for(int mask=0;mask<(1<<k);mask++)for(int j=0;j<3;j++){
			if(f[cur][mask][j]==0)continue;	
			for(int c=0;c<3;c++){
				if(to[j][c]<3){
					upd(f[nxt][g[mask][c]][to[j][c]],f[cur][mask][j]);
				}
			}
		}
	}
	for(int mask=0;mask<(1<<k);mask++){
		work_a(mask);
		for(int i=0;i<3;i++){
			upd(ans[a[k-1]],f[(n-1)&1][mask][i]);
		}
	}
	for(int i=0;i<=k;i++)printf("%d\n",ans[i]);
	return 0;
}
/*inline? ll or int? size? min max?*/