「Codeforces 808G」Anthem of Berland - KMP + DP

题意描述

Codeforces 链接

给定 \(s\) 串和 \(t\) 串,其中 \(s\) 串包含小写字母和问号,\(t\) 串只包含小写字母。

假设共有 \(k\) 个问号。

你需要给把每个问号变成一个小写字母,共有 \(26^k\) 种可能。

对于每种可能,设 \(t\) 匹配 \(s\) 的次数为 \(f_i\),请输出 \(\max(f_i)\)

\(1\leq |s|,|t|\leq 10^5,|s|\cdot |t|\leq 10^7\)

解题思路

对于匹配字符串,显然我们可以用 KMP 思想解决。而对于匹配计数,我们可以使用 DP 解决。

我们可以先使用 KMP 求出 \(t\)\(\text{next}\) 数组。

假设 \(s\) 长度为 \(n\)\(t\) 长度为 \(m\)。我们可以设 \(f_{i, j}\) 表示使用 KMP 匹配时 \(s_i\) 匹配到 \(t_j\) 情况下匹配成功的次数。假设通过 KMP 匹配可得 \(s_{i + 1}\) 匹配 \(t_k\),于是我们可以得到下列状态转移方程:

\[ f_{i + 1, k} = \max\{ f_{i + 1, k}, f_{i, j} + [k = m] \} \]

于是我们只需要枚举 \(k\) 即可。而对于每个 \(t_i\),我们可以预处理出 \(26\) 种字符所对应的 KMP 跳跃情况。这样可将时间复杂度降至 \(O(nm)\)

接下来我们需要解决的是如何在时间复杂度为 \(O(m)\) 情况下预处理 \(t_i\) 的跳跃情况。假设 \(\text{jump}_{i, j}\) 表示已经匹配 \(t_i\),在匹配 \(t_{i + 1}\) 与字符 \(j\) 进行匹配时跳转的坐标。通过 KMP 匹配,我们可以很显然得得出:\(\text{jump}_{i, t_{i + 1}} = i + 1\),且只有这种情况坐标增加,其余均为 \(\text{jump}_{i, j} = \text{jump}_{\text{next}_i, j}\)。于是我们可以通过递推预处理该数组。时间复杂度 \(O(m)\)

时间复杂度为 \(O(nm)\)

代码演示

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <climits>
#include <iostream>
#include <string>
#include <vector>
#include <array>
#include <algorithm>

int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);

std::string s, t;
std::cin >> s >> t;
int n = s.size(), m = t.size();
s = ' ' + s, t = ' ' + t + ' ';

std::vector<int> next(m + 1);
next[1] = 0;
for (int i = 2, j = 0; i <= m; i++) {
while (j && t[j + 1] != t[i]) j = next[j];
if (t[j + 1] == t[i]) j++;
next[i] = j;
}

std::vector<std::array<int, 'z' + 1>> jump(m + 1);
jump[0][t[1]] = 1;
for (int i = 1; i <= m; i++) {
for (int j = 'a'; j <= 'z'; j++) {
if (i < m && j == t[i + 1]) jump[i][j] = i + 1;
else jump[i][j] = jump[next[i]][j];
}
}

std::vector<std::vector<int>> f(n + 1, std::vector<int>(m + 1, INT_MIN));
f[0][0] = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j <= m; j++) {
if (f[i][j] != INT_MIN) {
if (s[i + 1] == '?') {
for (int c = 'a'; c <= 'z'; c++) {
f[i + 1][jump[j][c]] = std::max(f[i + 1][jump[j][c]], f[i][j] + (jump[j][c] == m));
}
} else f[i + 1][jump[j][s[i + 1]]] = std::max(f[i + 1][jump[j][s[i + 1]]], f[i][j] + (jump[j][s[i + 1]] == m));
}
}
}

std::cout << *std::max_element(f[n].begin(), f[n].end()) << std::endl;

return 0;
}