P4590 [TJOI2018]游园会 dp套dp+lcs

题目描述

有两个串,一个模式串,长度为K,一个匹配串(需要自己去凑),长度N,满足以下条件:
1.两个串只含“N”、“O”、“I”三种字符
2.匹配串不能出现子串“NOI”
对于每种匹配串和模式串的最长公共子序列长度 假设为s,求每个s对应多少种不同的匹配串
1 ≤ K ≤ 15 , 1 ≤ N ≤ 1000 1\leq K \leq 15,1\leq N \leq 1000 1K15,1N1000

题解

首先我们可以很容易想到一个dp方程,对于匹配串匹配到第i位,对应模式串现在的匹配情况为j,而且NOI在末尾出现的情况为0/1/2
d p [ i ] [ j ] [ 0 / 1 / 2 ] dp[i][j][0/1/2] dp[i][j][0/1/2]
至于对于模式串的转移,其实是有技巧的,假设模式串当前状态是psta,后面加一个字符j,我们不可以直接在后面找一个字符j,因为有可能虽然后面没有这个字符,但是以后也会用到。

比如模式串为:NOO,然后当前状态为6,就是选了后面的OO,然后现在要加个N

其实如果不选N,状态还是OO,那么以后的状态可能会出现问题。
如果选了N,转移出来的状态会是什么样子呢?

答案是转移到5,就是选第一个N和第三个O。(不能选第二个,因为以后可能再来一个O)

所以应该怎么转移呢,其实就是一个LCS:

rep(p,0,k-1) h[p] = g[p] = 0;
rep(p,0,k-1) if(i&(1ll<<p)) g[p] = 1; else g[p] = 0;
rep(p,0,k-1) g[p] = (g[p-1] + g[p]) % mod;
rep(p,0,k-1) h[p] = max(h[p-1] , max(g[p] , (g[p-1] + 1) * (s[p+1] == str[j])) );
rep(p,0,k-1) if(h[p] - h[p-1]) psta[i][j] |= (1ll << p);

大概的思想就是,对于状态的第i位数,与模式串1-i的最长公共子序列就是前i个数中1的数量,然后要是这样LCS再差分,就会使1都在前面尽量出现。

代码

#include <bits/stdc++.h>

#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define dep(i,a,b) for(int i=(a);i>=(b);--i)
#define fi first
#define se second
#define MP make_pair
#define PB push_back

#define int long long

const int N = 5e6+10;
const int mod = 1e9 + 7;

using namespace std;

inline int rd() {
  int p=0; int f=1; char ch=getchar();
  while(ch<'0' || ch>'9'){if(ch=='-') f*=-1; ch=getchar();}
  while(ch>='0' && ch<='9'){p=p*10+ch-'0'; ch=getchar();}
  return p*f;
}
char str[4]={"NOI"};
char s[N]; int f[2][33000][4]; int psta[33000][4];
void add(int &x,int y){x=(x+y)%mod;}
int ans[16]; int g[16],h[16]; 

signed main() {
  freopen("a.in","r",stdin);
  int n = rd(); int k = rd();
  rep(i,1,k) scanf("\n%c",&s[i]);
  int mx = (1ll << k) - 1;
  rep(i,0,mx) {
    rep(j,0,2) {
    	rep(p,0,k-1) h[p] = g[p] = 0;
    	rep(p,0,k-1) if(i&(1ll<<p)) g[p] = 1; else g[p] = 0;
			rep(p,0,k-1) g[p] = (g[p-1] + g[p]) % mod;
			rep(p,0,k-1) h[p] = max(h[p-1] , max(g[p] , (g[p-1] + 1) * (s[p+1] == str[j])) );
			rep(p,0,k-1) if(h[p] - h[p-1]) psta[i][j] |= (1ll << p);
//			printf("%lld %lld : %lld\n",i,j,psta[i][j]);
		}
	}
	
	int op = 0; f[0][0][0] = 1;
	rep(i,1,n) {
		memset(f[op^1],0,sizeof(f[op^1]));
	  rep(j,0,mx) {
	  	rep(k,0,2) // 前k个相同 
	  	  rep(p,0,2) {
				  if(p == k) {
	  	      if(k!=2) add(f[op^1][psta[j][p]][k+1] , f[op][j][k]);
	  	    }
	  	    else {
					  if(p==0) add(f[op^1][psta[j][p]][1] , f[op][j][k]);
					  else add(f[op^1][psta[j][p]][0] , f[op][j][k]);
					}
	  	  }
		}op ^= 1;
	}
 
  rep(i,0,mx) rep(j,0,2) add(ans[__builtin_popcount(i)],f[op][i][j]);

  rep(i,0,k) printf("%lld\n",ans[i]);

  return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值