BZOJ3864 Hero meet devil

题意:有一个字符集为 A, C, G, T 的字符串 $S$,问有多少个长度为 $n$ 的字符串 $T$ 满足 $\mathrm{LCS}(S, T) = i$。你需要输出所有 $i\in [0, |S|]$ 的答案。$|S|\leq 15$,$n\leq 1000$。

DP of DP 模板题。

首先考虑如果我们已经知道了 $T$,怎么计算 $\mathrm{LCS}(S, T)$。DP 过程不再赘述,这个 DP 过程结果可以由 $|S|$ 个数来代表,分别为每一个前缀和 $T$ 的 $\mathrm{LCS}$。

注意到一旦我们知道了这 $|S|$ 个数,前面的 $T$ 如何我们其实是不关心的。也就是说,我们可以把这 $|S|$ 个数变成状态,然后枚举 $T$ 的下一个字符,直接转移就可以做完。

但是注意到一个问题是这 $|S|$ 个数可能都比较大,不好压入状态。但是 $\mathrm{LCS}$ 的特殊性质告诉我们 $dp_i\leq dp_{i - 1} + 1$。因为一旦 $dp_i$ 匹配到了一个 $>dp_{i - 1} + 1$ 的东西,删掉最后一个数就可以得到 $dp_i - 1$ 更新 $dp_{i - 1}$。

这样我们就可以把差分数组压入状态,容易发现最多只有 $2 ^ {|S|}$ 种状态,就可以计算了,时间复杂度 $O(n 2^{|S|} \sum)$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
void work()
{
std::cin >> s >> m;
n = strlen(s);
for (int sta = 0; sta < (1 << n); ++ sta)
for (int j = 0; j < 4; ++ j)
{
static int f[N], g[N];
f[0] = sta & 1;
for (int i = 1; i < n; ++ i) f[i] = f[i - 1] + (sta >> i & 1);
g[0] = f[0];
if (s[0] == ch[j]) chkmax(g[0], 1);
for (int i = 1; i < n; ++ i)
{
g[i] = g[i - 1], chkmax(g[i], f[i]);
if (s[i] == ch[j]) chkmax(g[i], f[i - 1] + 1);
}
int to = g[0];
for (int i = 1; i < n; ++ i)
if (g[i] != g[i - 1]) to |= 1 << i;
/*std::cout << "Check " << sta << ' ' << j << ' ' << to << '\n';
for (int i = 0; i < n; ++ i) printf("%d ", g[i]);
puts("");*/
trs[sta][j] = to;
}
for (int sta = 0; sta < (1 << n); ++ sta) f[0][sta] = !sta;
for (int i = 1; i <= m; ++ i) {
for (int sta = 0; sta < (1 << n); ++ sta) f[i & 1][sta] = 0;
for (int sta = 0; sta < (1 << n); ++ sta)
for (int j = 0; j < 4; ++ j)
adj(f[i & 1][trs[sta][j]] += f[(i - 1) & 1][sta] - Mod);
}
static int ans[N + 1];
for (int i = 0; i <= n; ++ i) ans[i] = 0;
for (int s = 0; s < (1 << n); ++ s)
adj(ans[__builtin_popcount(s)] += f[m & 1][s] - Mod);
for (int i = 0; i <= n; ++ i) printf("%d\n", ans[i]);
}