题目传送门:CF808G Anthem of Berland


statement:

给定一个长度为\(n\)的字符串S,其中有一些字符是?,再给定一个长度为\(m\)的字符串T,求在问号中填入合法字符的所有情况中,TS中出现次数的最大值。

\(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;
}