「TJOI 2018」游园会

传送门


problem

给定一个模式串 S S S,长度为 k k k,只含字符 'N','O','I'。对于 ∀ i ∈ [ 0 , k ] \forall i\in[0,k] i[0,k],求出满足以下条件的串的数量:

  1. 长度为 n n n 且只含字符 'N','O','I'
  2. S S S最长公共子序列长度恰好为 i i i
  3. 不含子串 NOI

答案对 1 0 9 + 7 10^9+7 109+7 取模。

数据范围: n ≤ 1000 n≤1000 n1000 k ≤ 15 k≤15 k15


solution

参考博客

首先考虑一下最长上升子序列怎么求,设 d p i , j dp_{i,j} dpi,j 表示 A A A 串前 i i i 个字符, B B B 串前 j j j 个字符的答案,则:

l c s i , j = m a x { l c s i − 1 , j , l c s i , j − 1 , l c s i − 1 , j − 1 + [ A i = B j ] } \mathrm{lcs}_{i,j}=max\{\mathrm{lcs}_{i-1,j},\mathrm{lcs}_{i,j-1},\mathrm{lcs}_{i-1,j-1}+[A_i=B_j]\} lcsi,j=max{lcsi1,j,lcsi,j1,lcsi1,j1+[Ai=Bj]}

B B B 串就是题中的模式串 S S S,是已知的。也就是说,我们要求 l c s i \mathrm{lcs}_i lcsi 这一行,只需要 l c s i − 1 \mathrm{lcs}_{i-1} lcsi1 这一行和 A i A_i Ai

发现 l c s i , j − l c s i , j − 1 ≤ 1 \mathrm{lcs}_{i,j}-\mathrm{lcs}_{i,j-1}\le 1 lcsi,jlcsi,j11,那么对 l c s i \mathrm{lcs}_i lcsi 差分一下会是一个长度为 k k k 01 01 01,是可以状态压缩的!

那么我们可以再设计一个 d p dp dp f i , j , 0 / 1 / 2 f_{i,j,0/1/2} fi,j,0/1/2 表示 d p dp dp 到第 i i i 位, l c s i \mathrm{lcs}_i lcsi 的状态为 j j j,第 i i i 位填 'N' / / /'O' / / /'I' 的方案数。那么,我们可以 l i s i \mathrm{lis}_i lisi 还原成数组,按照 l i s \mathrm{lis} lis d p dp dp 转移后得到新的状态,再对填 'N' / / /'O' / / /'I' 简单分类讨论一下得到转移方程。

注意在统计答案的时候,若状态 i i i 中有 n u m num num 1 1 1,那么 i i i 的贡献应该加到 n u m num num 里(因为 i i i 是经过差分后再状压的)。

时间复杂度 O ( n k 2 k ) O(nk2^k) O(nk2k)


code

#include<bits/stdc++.h>
using namespace std;
const int N=(1<<15)+5,P=1e9+7;
int n,k,f[2][N][3]={1},bit[N],ans[1005];
void Add(int &x,int y)  {x=(x+y>=P)?x+y-P:x+y;}
int A[20],B[20];
void Get_array(int sta){
	for(int i=0;i<k;++i)  A[i+1]=(sta>>i)&1;
	for(int i=1;i<=k;++i)  A[i]+=A[i-1];
}
int Get_sta(){
	int sta=0;
	for(int i=0;i<k;++i)  sta|=((B[i+1]-B[i])<<i);
	return sta;
}
char S[20];
void dp(int t,int j,int num,char c,int val){
	Get_array(j);
	for(int i=1;i<=k;++i)  B[i]=max(max(A[i],B[i-1]),A[i-1]+(c==S[i]));
	int x=Get_sta();
	Add(f[t][x][num],val);
}
int main(){
	scanf("%d%d%s",&n,&k,S+1);
	int sta=(1<<k),t=1;
	for(int i=1;i<=n;++i,t^=1){
		memset(f[t],0,sizeof(f[t]));
		for(int j=0;j<sta;++j){
			if(f[t^1][j][0]){
				dp(t,j,1,'N',f[t^1][j][0]),
				dp(t,j,0,'O',f[t^1][j][0]),
				dp(t,j,0,'I',f[t^1][j][0]);
			}
			if(f[t^1][j][1]){
				dp(t,j,1,'N',f[t^1][j][1]),
				dp(t,j,2,'O',f[t^1][j][1]),
				dp(t,j,0,'I',f[t^1][j][1]);
			}
			if(f[t^1][j][2]){
				dp(t,j,1,'N',f[t^1][j][2]),
				dp(t,j,0,'O',f[t^1][j][2]);
			}
		}
	}
	for(int i=0;i<sta;++i)  bit[i]=bit[i>>1]+(i&1);
	for(int i=0;i<sta;++i)
		for(int j=0;j<3;++j)  Add(ans[bit[i]],f[t^1][i][j]);
	for(int i=0;i<=k;++i)  printf("%d\n",ans[i]);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值