洛谷传送门
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) (N≤1000,K≤15)
第二行一共 K K K个字符,表示小豆得到奖章串。
输出格式:
一共 K + 1 K+1 K+1行,第行表示小豆最后奖励等级为 i − 1 i-1 i−1的不同的合法兑奖串的个数,可能这个数会很大,结果对 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(26−6−1)种为最长公共子序列长度为 1 1 1。
数据范围
对于 10 % 10\% 10%的数据, N ≤ 10 , K ≤ 10 N\leq10,K\leq10 N≤10,K≤10
对于 30 % 30\% 30%的数据, N ≤ 100 , K ≤ 4 N\leq100,K\leq4 N≤100,K≤4
对于 100 % 100\% 100%的数据, N ≤ 1000 , K ≤ 15 N\leq1000,K\leq15 N≤1000,K≤15
解题分析
很妙的一道题…
假设兑换串为
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[i−1][j],dp[i][j−1],dp[i−1][j−1]+[A[i]=B[j]])
发现我们只需要知道
d
p
[
i
−
1
]
dp[i-1]
dp[i−1]和
A
[
i
]
A[i]
A[i]即可从
d
p
[
i
−
1
]
dp[i-1]
dp[i−1]转移到
d
p
[
i
]
dp[i]
dp[i]。
所以我们只需要状压记下前 i − 1 i-1 i−1个字符和奖章串的匹配情况, 再枚举第 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][j−1]∣≤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]);
}