题解: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;
}
本文来自博客园,作者:xuyifei0302,转载请注明原文链接:https://www.cnblogs.com/xuyifei0302/p/22828937

浙公网安备 33010602011771号