Educational Codeforces Round 21 G. Anthem of Berland(DP+KMP)
原题链接:G. Anthem of Berland
题目大意:
给出一个含有 ? ? ? 字符的字符串 s s s 和一个字符串 t t t,其中 ? ? ? 可以是 26 26 26 个字符中任意一个字符。
假设 s s s 有 k k k 个 ? ? ? ,现在要你求出在所有 2 6 k 26^{k} 26k 种可能的情况中, t t t 在 s s s 出现的次数最大值为多少。
解题思路:
注意到 ∣ s ∣ ⋅ ∣ t ∣ ≤ 1 0 7 |s| \cdot |t| \leq 10^{7} ∣s∣⋅∣t∣≤107,考虑某种 O ( n m ) O(nm) O(nm) 的做法。
定义 d p [ i ] dp[i] dp[i] 为只考虑 s s s 的前 i i i 个字符, t t t 能匹配的最大次数。
比较简单的想法就是考虑当前位置放和不放一整串 t t t 。(前提是能放)
d p [ i ] = max { d p [ i − 1 ] , d p [ i − ∣ t ∣ ] + 1 } \begin{aligned} dp[i] = \max\{dp[i-1],dp[i-|t|]+1\} \end{aligned} dp[i]=max{dp[i−1],dp[i−∣t∣]+1}
但是显然这不能考虑到所有的情况,比如当 s = a b a b a , t = a b a s=ababa,t=aba s=ababa,t=aba 时,我们可以利用前面放下的 t t t 串再做一次匹配,即:
s = [ a b a b a ] s=[ababa] s=[ababa]
t = [ a b a _ _ ] t=[aba\_\_] t=[aba__]
t = [ _ _ a b a ] t=[\_\_aba] t=[__aba]
此时 d p [ 5 ] dp[5] dp[5] 可以从 d p [ 3 ] dp[3] dp[3] 转移而来,发现这是一个 b o r d e r border border,考虑用 k m p kmp kmp 的 n e x t next next 数组来优化这个寻找可以从哪个 b o r d e r border border 转移过程。
但能从 b o r d e r border border 开始转移的前提是,这个位置 必须 放了一个 t t t ,考虑下面的情况:
s = [ . . . a c [ a ] b a ] s=[...ac[a]ba] s=[...ac[a]ba]
t = [ . . . _ _ [ a ] b a ] t=[...\_\_[a]ba] t=[...__[a]ba]
此时虽然 [ a ] [a] [a] 位置确实是我们 t = a b a t=aba t=aba 的 b o r d e r border border,但是其前面显然没有办法放下一个串 t t t,因而这个转移是不合法的。
所以我们还要考虑增加一个辅助数组 g [ i ] g[i] g[i] 表示,当前只考虑前 i i i 个位置,且第 i i i 为 必须 放一个串 t t t 的最大方案数。
那么此时转移就是考虑要不要从 b o r d e r border border 处转移即可:
d p [ i ] = max j ∈ b o r d e r { d p [ i ] , g [ j ] + 1 } \begin{aligned} dp[i] = \max_{j \in border}\{dp[i],g[j]+1\} \end{aligned} dp[i]=j∈bordermax{dp[i],g[j]+1}
那么直接暴力匹配即可,考虑当前如果不能放 t t t,则 d p [ i ] = d p [ i − 1 ] dp[i] = dp[i - 1] dp[i]=dp[i−1] 。
否则考虑是否从 b o r d e r border border 转移而来,或者直接放下一个串 t t t 。
其中串 s s s 中的 ? ? ? 直接当通配符暴力匹配即可。
时间复杂度: O ( ∣ s ∣ ⋅ ∣ t ∣ ) O(|s| \cdot |t|) O(∣s∣⋅∣t∣)
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
std::string s, t;
std::cin >> s >> t;
int n = s.size(), m = t.size();
s = '_' + s, t = '_' + t;
std::vector<int> nxt(m + 1);
for (int j = 2, p = 0; j <= m; ++j) {
while (p && t[p + 1] != t[j]) p = nxt[p];
p = nxt[j] = p + (t[p + 1] == t[j]);
}
auto check = [&](int l) {
for (int i = 0; i < m; ++i) {
if (s[l + i] != '?' && s[l + i] != t[i + 1]) {
return false;
}
}
return true;
};
std::vector<int> dp(n + 1), g(n + 1);
for (int i = m; i <= n; ++i) {
dp[i] = dp[i - 1];
//假设能放串 t
if (check(i - m + 1)) {
g[i] = dp[i - m] + 1;
for (int p = nxt[m]; p; p = nxt[p]) {
g[i] = std::max(g[i], g[i - (m - p)] + 1);
}
dp[i] = std::max(dp[i], g[i]);
}
}
std::cout << dp[n] << "\n";
}
signed main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t = 1;
//std::cin >> t;
while (t--) {
solve();
}
return 0;
}