YC342A [ 20240922 CQYC NOIP 模拟赛 T1 ] 前缀(lcp)
题意
给定 \(n, m\) 以及长为 \(n\) 的字符串 \(S\)。
你需要构造字符串 \(T\) 使得 \(\sum LCP(S, T[i, ..., m])\) 最大。
求出这个最大值。
\(n, m \le 5 \times 10 ^ 3\)。
Sol
首先不难发现答案一定可以由若干 \(S\) 的前缀拼成。考虑找到最小的无法被拆为前缀的子段,显然这个子段无法产生任何贡献。
考虑一个 \(\texttt{dp}\),设 \(f_{i, j}\) 表示当前放了前 \(i\) 个位置,放到 \(S\) 的前缀 \(j\),的答案。
但是这个东西有一个问题,会算漏很多贡献,因为前一个前缀对后面的前缀都是有贡献的。
因此考虑 \(border\),不难发现事实上添加一个点的贡献就是她的最大 \(border\) 的贡献 $ + 1$。
- \(f_{i, j} \leftarrow f_{i - 1, j - 1} + cnt_{j}\)。
- \(f_{i, nxt_j} \leftarrow f_{i, j}\)。
做完了,时间复杂度:\(O(n \times m)\)。
Code
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <array>
using namespace std;
#ifdef ONLINE_JUDGE
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
char buf[1 << 23], *p1 = buf, *p2 = buf, ubuf[1 << 23], *u = ubuf;
#endif
int read() {
int p = 0, flg = 1;
char c = getchar();
while (c < '0' || c > '9') {
if (c == '-') flg = -1;
c = getchar();
}
while (c >= '0' && c <= '9') {
p = p * 10 + c - '0';
c = getchar();
}
return p * flg;
}
void write(int x) {
if (x < 0) {
x = -x;
putchar('-');
}
if (x > 9) {
write(x / 10);
}
putchar(x % 10 + '0');
}
bool _stmer;
const int N = 5e3 + 5;
array <int, N> s;
namespace Kmp {
array <int, N> isl;
void prefix(int n) {
for (int i = 2; i <= n; i++) {
isl[i] = isl[i - 1];
while (isl[i] && s[i] != s[isl[i] + 1]) isl[i] = isl[isl[i]];
if (s[i] == s[isl[i] + 1]) isl[i]++;
}
}
} //namespace Kmp
array <array <int, N>, N> f;
array <int, N> cnt;
bool _edmer;
int main() {
cerr << (&_stmer - &_edmer) / 1024.0 / 1024.0 << "MB\n";
int n = read(), m = read();
for (int i = 1; i <= n; i++) s[i] = read();
Kmp::prefix(n);
for (int i = 1; i <= n; i++)
cnt[i] = cnt[Kmp::isl[i]] + 1;
f[0].fill(-2e9), f[0][0] = 0;
for (int i = 1; i <= m; i++) {
f[i].fill(-2e9);
for (int j = n; j; j--) {
f[i][j] = max(f[i][j], f[i - 1][j - 1] + cnt[j]);
f[i][Kmp::isl[j]] = max(f[i][Kmp::isl[j]], f[i][j]);
}
}
int ans = 0;
for (int i = 1; i <= n; i++) ans = max(ans, f[m][i]);
write(ans), puts("");
return 0;
}