题解:
据说这样的套路叫DP套DP?之前也做过一道最长上升子序列,大概思路都是在DP中表示另外一个DP数组的状态,比如这道题就是利用 f[i][j] f [ i ] [ j ] 与 f[i][j−1] f [ i ] [ j − 1 ] 最多只相差 1 1 (表示兑奖串前 i i 位与奖章串前位的最长公共子序列),利用差分+状态压缩表示出某一 i i 的所有,然后预处理每个状态在填了某个字母后的转移,直接转移即可。对于不能出现 NOI N O I 的限制,用一维来表示最后几个字母的状态就行了。
代码:
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define pa pair<int,int>
//NOI
//012
const int Maxn=1010;
const int Maxk=20;
const int mod=1000000007;
void upd(int &x,int y){x+=y;if(x>=mod)x-=mod;}
char s[Maxk];
int N,K,a[Maxk],to[32770][3],f[2][3][32770];//0 1:N 2:NO
int get(char c)
{
if(c=='N')return 0;
if(c=='O')return 1;
return 2;
}
int t1[Maxk],t2[Maxk];
void work(int S)
{
for(int i=0;i<3;i++)
{
t1[0]=t2[0]=0;
for(int j=0;j<K;j++)t1[j+1]=t1[j]+((S&(1<<j))?1:0);
for(int j=1;j<=K;j++)
{
t2[j]=max(t1[j],t2[j-1]);
if(a[j]==i)t2[j]=max(t2[j],t1[j-1]+1);
}
int tmp=0;
for(int j=1;j<=K;j++)if(t2[j]>t2[j-1])tmp|=(1<<(j-1));
to[S][i]=tmp;
}
}
int one(int x)
{
int re=0;
while(x)
{
re+=(x&1);
x>>=1;
}
return re;
}
int ans[Maxk];
int main()
{
scanf("%d%d%s",&N,&K,s+1);
for(int i=1;i<=K;i++)a[i]=get(s[i]);
for(int i=0;i<(1<<K);i++)work(i);
int now=0;
memset(f[now],0,sizeof(f[now]));
f[0][0][0]=1;
for(int i=0;i<N;i++)
{
now^=1;
memset(f[now],0,sizeof(f[now]));
for(int S=0;S<(1<<K);S++)
{
int t;
t=f[now^1][0][S];//
upd(f[now][1][to[S][0]],t);//N
upd(f[now][0][to[S][1]],t);//O
upd(f[now][0][to[S][2]],t);//I
t=f[now^1][1][S];//N
upd(f[now][1][to[S][0]],t);//N
upd(f[now][2][to[S][1]],t);//O
upd(f[now][0][to[S][2]],t);//I
t=f[now^1][2][S];//NO
upd(f[now][1][to[S][0]],t);//N
upd(f[now][0][to[S][1]],t);//O
}
}
for(int i=0;i<3;i++)
for(int j=0;j<(1<<K);j++)
upd(ans[one(j)],f[now][i][j]);
for(int i=0;i<=K;i++)printf("%d\n",ans[i]);
}