题目传送门:CF808G Anthem of Berland
statement:
给定一个长度为\(n\)的字符串
S
,其中有一些字符是?
,再给定一个长度为\(m\)的字符串T
,求在问号中填入合法字符的所有情况中,T
在S
中出现次数的最大值。\(n,m\leq 500000\)
\(n\times m\leq 10^7\)
\(|\sum|=26\)
solution1:
考虑一个朴素的DP
,记\(f_{i,j}\)表示在字符串S
中匹配到了位置\(i\),当前在字符串T
中匹配到了位置\(j\)的时候匹配的最大值。预处理跳fail
链的过程,转移可以做到\(\mathcal O(|\sum|M)\),总时间复杂度为\(\mathcal O(|\sum|NM)\),空间复杂度为\(\mathcal O(NM)\)。
solution2:
还是考虑DP
,\(f_{i}\)表示考虑了S
中的前\(i\)个字符匹配数量的最大值。考虑如何转移,我们强制让前面放一个字符串T
,那么\(f_i\gets f_{i-m}+1\)。
接着考虑重叠的情况,也就是找到一些字符串T
的前缀等于T
的后缀的长度,令其为\(k_j\),那么有\(f_i\gets 1+\max f_{i-m+k_j}\)。
但是这样转移是不对的,因为转移的条件是有两个T
串重叠,\(f_{i-m+k_j}\)并不能保证这一点。
于是考虑开一个辅助数组\(g\),\(g_i\)表示强制在\(i\)之前放入一个T
串当前匹配数的最大值。
时间复杂度为\(\mathcal O(NM)\),空间复杂度为\(\mathcal O(N+M)\)。
code1:
#include <bits/stdc++.h>
const int MAXN = 1.1E5;
char A[MAXN];
char B[MAXN];
int Fail[MAXN];
int To[MAXN][26];
int n, m, ans;
inline void getFail() {
int j = 0;
for (int i = 2;i <= m; ++i) {
for (; j && (B[j + 1] != B[i]); j = Fail[j]) ;
Fail[i] = j += (B[j + 1] == B[i]);
}
}
int main(void) {
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
std::cin >> (A + 1);
std::cin >> (B + 1);
n = strlen(A + 1), m = strlen(B + 1), getFail();
if (m > n)
return puts("0") * 0;
for (int i = 0; i <= m; ++i)
for (int j = 0; j < 26; ++j) {
int k = i;
for (; k && ((B[k + 1] - 'a') != j); k = Fail[k]) ;
To[i][j] = k += (B[k + 1] - 'a') == j;
}
std::vector <std::vector <int>> dp(2, std::vector <int> (m, -1));
dp[0][0] = 0;
for (int i = 1; i <= n; ++i) {
for (int j = 0;j < m; ++j)
dp[i & 1][j] = -1;
int now = i & 1;
int pre = now ^ 1;
int nowC = A[i] - 'a';
for (int j = 0; j < m; ++j) {
if (!~dp[pre][j])
continue;
if (nowC >= 0) {
int toP = To[j][nowC];
if (toP == m)
toP = Fail[m], dp[now][toP] = std::max(dp[now][toP], dp[pre][j] + 1);
else
dp[now][toP] = std::max(dp[now][toP], dp[pre][j]);
} else {
for (int k = 0;k < 26; ++k) {
int toP = To[j][k];
if (toP == m)
toP = Fail[m], dp[now][toP] = std::max(dp[now][toP], dp[pre][j] + 1);
else
dp[now][toP] = std::max(dp[now][toP], dp[pre][j]);
}
}
}
}
std::cout << *std::max_element(dp[n & 1].begin(), dp[n & 1].end()) << '\n';
return 0;
}
code2:
#include <bits/stdc++.h>
const int MAXN = 1.1E5;
char A[MAXN];
char B[MAXN];
int main(void) {
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
std::cin >> (A + 1);
std::cin >> (B + 1);
int n = strlen(A + 1);
int m = strlen(B + 1);
std::vector <int> Fail(m + 1, 0);
std::vector <int> dp(n + 1, 0);
std::vector <int> g(n + 1, 0);
auto check = [&](int id) {
for (int i = 1;i <= m; ++i)
if (A[id - m + i] != B[i] && A[id - m + i] != '?')
return false;
return true;
};
for (int i = 2, j = 0; i <= m; ++i) {
for (; j && B[j + 1] != B[i]; j = Fail[j]) ;
Fail[i] = j += (B[j + 1] == B[i]);
}
for (int i = 1;i <= n; ++i) {
dp[i] = dp[i - 1];
if (check(i)) {
g[i] = dp[i - m] + 1;
int j = Fail[m];
for (; j; j = Fail[j])
g[i] = std::max(g[i], g[i - m + j] + 1);
dp[i] = std::max(dp[i], g[i]);
}
}
std::cout << dp[n] << '\n';
return 0;
}