[Luogu P4590] [BZOJ 5336] [TJOI2018]游园会

洛谷传送门
BZOJ传送门

题目描述

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

输入输出格式

输入格式:

第一行两个数, N , K N, K N,K分别代表兑奖串的长度,小豆收集的奖章串的长度。 ( N ≤ 1000 , K ≤ 15 ) (N\leq1000,K\leq15) (N1000,K15)

第二行一共 K K K个字符,表示小豆得到奖章串。

输出格式:

一共 K + 1 K+1 K+1行,第行表示小豆最后奖励等级为 i − 1 i-1 i1的不同的合法兑奖串的个数,可能这个数会很大,结果对 1 0 9 + 7 10^9+7 109+7取模。

输入输出样例

输入样例#1:
3 2
NO
输出样例#1:
1
19
6

说明

样例解释

最长公共子序列长度 0 0 0的串有:III;

最长公共子序列长度 2 2 2的串有:NON,NNO,NOO,ONO,INO,NIO;

除去 N O I NOI NOI,余下的 19 ( 26 − 6 − 1 ) 19(26-6-1) 19(2661)种为最长公共子序列长度为 1 1 1

数据范围

对于 10 % 10\% 10%的数据, N ≤ 10 , K ≤ 10 N\leq10,K\leq10 N10,K10

对于 30 % 30\% 30%的数据, N ≤ 100 , K ≤ 4 N\leq100,K\leq4 N100,K4

对于 100 % 100\% 100%的数据, N ≤ 1000 , K ≤ 15 N\leq1000,K\leq15 N1000,K15

解题分析

很妙的一道题…

假设兑换串为 A A A, 奖章串为 B B B d p [ i ] [ j ] dp[i][j] dp[i][j]表示 A A A i i i个字符, B B B j j j个字符的最长公共子序列长度, 就有如下转移:
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] , d p [ i − 1 ] [ j − 1 ] + [ A [ i ] = B [ j ] ] ) dp[i][j]=max(dp[i-1][j], dp[i][j-1],dp[i-1][j-1]+[A[i]=B[j]]) dp[i][j]=max(dp[i1][j],dp[i][j1],dp[i1][j1]+[A[i]=B[j]])
发现我们只需要知道 d p [ i − 1 ] dp[i-1] dp[i1] A [ i ] A[i] A[i]即可从 d p [ i − 1 ] dp[i-1] dp[i1]转移到 d p [ i ] dp[i] dp[i]

所以我们只需要状压记下前 i − 1 i-1 i1个字符和奖章串的匹配情况, 再枚举第 i i i个字符, 就可以转移了。

然而如何状压一个数组? 观察到 ∣ d p [ i ∣ [ j ] − d p [ i ] [ j − 1 ] ∣ ≤ 1 |dp[i|[j]-dp[i][j-1]|\le 1 dp[i[j]dp[i][j1]1, 所以可以差分一下转化为一个 01 01 01序列, 就可以压在一个 i n t int int里面了。

转移可以通过预处理先搞出来。

总复杂度 O ( ( N + K ) 2 K ) O((N+K)2^K) O((N+K)2K)

代码如下:

#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#define R register
#define IN inline
#define ll long long
#define MX 1050
#define MOD 1000000007
template <class T> IN T max(T a, T b) {return a > b ? a : b;}
template <class T> IN T min(T a, T b) {return a < b ? a : b;}
const int SIZ = 40050;
int nex[SIZ][3], ans[20];
int buf1[20], buf2[20];
int dp[2][SIZ][3];
char mdu[20];
int n, k, all;
IN void dct(R int tar)
{
	for (R int i = 0; i < k; ++i) buf1[i + 1] = (tar >> i) & 1;
	for (R int i = 2; i <= k; ++i) buf1[i] += buf1[i - 1];
}
IN int ect()
{
	int ret = 0;
	for (R int i = 1; i <= k; ++i) if(buf2[i] ^ buf2[i - 1]) ret |= 1 << (i - 1);
	return ret;
}
IN int get(R int id, R char tar)
{
	dct(id);
	for (R int i = 1; i <= k; ++i)
	buf2[i] = max(max(buf2[i - 1], buf1[i]), buf1[i - 1] + (mdu[i] == tar));
	return ect();
}
IN void add(int &tar, R int ad) {tar += ad; if (tar >= MOD) tar -= MOD;}
int main(void)
{
	R int cur = 0, pre = 1;
	scanf("%d%d", &n, &k);
	scanf("%s", mdu + 1);
	all = (1 << k) - 1;
	for (R int i = 0; i <= all; ++i)
	{
		nex[i][0] = get(i, 'N');
		nex[i][1] = get(i, 'O');
		nex[i][2] = get(i, 'I');
	}
	dp[cur][0][0] = 1;
	for (R int i = 1; i <= n; ++i)
	{
		std::swap(cur, pre);
		std::memset(dp[cur], 0, sizeof(dp[cur]));
		for (R int s = 0; s <= all; ++s)
		{
			if (dp[pre][s][0])
			{
				add(dp[cur][nex[s][0]][1], dp[pre][s][0]);
				add(dp[cur][nex[s][1]][0], dp[pre][s][0]);
				add(dp[cur][nex[s][2]][0], dp[pre][s][0]);
			}
			if (dp[pre][s][1])
			{
				add(dp[cur][nex[s][0]][1], dp[pre][s][1]);
				add(dp[cur][nex[s][1]][2], dp[pre][s][1]);
				add(dp[cur][nex[s][2]][0], dp[pre][s][1]);
			}
			if (dp[pre][s][2])
			{
				add(dp[cur][nex[s][0]][1], dp[pre][s][2]);
				add(dp[cur][nex[s][1]][0], dp[pre][s][2]);
			} 
		}
	}
	for (R int i = 0; i <= all; ++i)
	{
		R int s = __builtin_popcount(i);
		for (R int j = 0; j < 3; ++j) add(ans[s], dp[cur][i][j]);
	}
	for (R int i = 0; i <= k; ++i) printf("%d\n", ans[i]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值