题解:P14874 [ICPC 2020 Yokohama R] Suffixes may Contain Prefixes

闲话:KMP 自动机是个好东西。

我们读完题,考虑如何去算我们将要构造的数组 \(b\) 的贡献。由于价值匹配模式串 \(t\) 的前缀有关,我们考虑用 KMP 自动机来维护当前匹配前缀。

当处理到 \(i\) 步的时候,我们会得到一个状态 \(q\),表示 \(q\) 是满足条件 \(b_{i - q + 1}\cdots b_i = t_1\cdots t_{q}\) 的最长长度。

那么,所有在位置 \(i\) 仍然对答案产生贡献的后缀,其匹配长度恰好是其对应的 \(q\) 的所有包含 \(q\) border 长度。令 \(q_i\) 表示 \(i\) 步所对的 \(q\),则价值就为 \(\sum_{i = 1}^{n} F(q_i)\)。其中 \(F(q_i)\) 就表示 \(t\) 的前 \(q_i\) 个字符组成的串的包含自身的 border 的数量。

那么我们就可以去想如何去求出 \(F(q_i)\)。

众所周知,在 KMP 中,对于一个长度为 \(q\) 的前缀,其最长真 border 长度是 \(pi[q - 1]\)。其会构成出一个 border 链:\(q, pi[q - 1], pi[pi[q - 1] - 1], \dots, 0\)。

则有 \(F(0) = 0, F(q) = F(pi[q - 1]) + 1\),其中 \(1 \le q \le m\),其中 \(m\) 是模式串的长度。

那么我们就可以之间套在 KMP 自动机上做 DP。

有 KMP 自动机的状态定义,则有对应的转移式:
我们设状态转移数组 \(nxt[q][c]\) 表示在状态 \(q\) 读入字符 \(c\) 后到达的新状态。那么有转移:

  • 若 \(q < m\) 且 \(c = t[q + 1]\),则可以继续向下匹配,因为下一位还是相同。那么状态为 \(nxt[q][c] = q + 1\)。
  • 若下一位不同,但是 \(q > 0\),则失配,需要回退,\(nxt[q][c] = nxt[pi[q - 1]][c]\)。
  • 若下一位不同,且当且 \(q = 0\),则没有办法匹配,直接退回原点 \(nxt[q][c] = 0\)。

我们设 \(dp[q]\) 表示处理完当前这一步之后,处于状态 \(q\) 时所获得的最大总分数。

我们对于每一步,去枚举当前所对的状态 \(q\) 与将要转移的字符 \(c\),将其转移到 \(nxt[q][c]\)。

设当前在第 \(i\) 步,则有 \(dp[i + 1][nxt[q][c]] = \max(dp[i + 1][nxt[q][c]], dp[i][q] + F[nxt[q][c]])\)。

则答案为最大的 \(dp[n][q]\),其中 \(0 \le q \le m\)。

时间复杂度 \(O(nm)\),空间复杂度 \(O(m)\)。

详见代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int INF = 1e16;
int n, m, pi[2005], w[2005], nxt[2005][26], s[2005], dp[2][2005];
string t;
signed main() {
	ios_base::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin >> t >> n;
	m = t.size();
	for (int i = 0; i < m; i ++) {
		s[i] = t[i] - 'a';
	}
	for (int i = 1; i < m; i ++) {
		int j = pi[i - 1];
		while (j && t[i] != t[j]) {
			j = pi[j - 1];
		}
		if (t[i] == t[j]) {
			j ++;
		}
		pi[i] = j;
	}
	for (int i = 1; i <= m; i ++) {
		w[i] = w[pi[i - 1]] + 1;
	}
	for (int i = 0; i <= m; i ++) {
		for (int j = 0; j < 26; j ++) {
			if (i < m && s[i] == j) {
				nxt[i][j] = i + 1;
			} else if (i) {
				nxt[i][j] = nxt[pi[i - 1]][j];
			} else {
				nxt[i][j] = 0;
			}
		}
	}
	fill(dp[0], dp[0] + m + 2, -INF);
	fill(dp[1], dp[1] + m + 2, -INF);
	dp[0][0] = 0;
	for (int i = 1; i <= n; i ++) {
		fill(dp[i & 1], dp[i & 1] + m + 2, -INF);
		for (int j = 0; j <= m; j ++) {
			if (dp[(i & 1) ^ 1][j] == -INF) {
				continue;
			}
			for (int k = 0; k < 26; k ++) {
				dp[i & 1][nxt[j][k]] = max(dp[i & 1][nxt[j][k]], dp[(i & 1) ^ 1][j] + w[nxt[j][k]]);
			}
		} 
	}
	int maxn = 0;
	for (int i = 0; i <= m; i ++) {
		maxn = max(maxn, dp[n & 1][i]);
	}
	cout << maxn;
	return 0;
}
posted @ 2026-09-03 17:27  xuyifei0302  阅读(6)  评论(0)    收藏  举报