题解:P3546 [POI 2012] PRE-Prefixuffix
题目描述
对于两个串 \(S_1, S_2\),如果能够将 \(S_1\) 的一个后缀移动到开头后变成 \(S_2\),就称 \(S_1\) 和 \(S_2\) 循环相同。例如串 ababba 和串 abbaab 是循环相同的。
给出一个长度为 \(n\) 的串 \(S\),求满足下面条件的最大的 \(L(L\leq \frac n 2)\):\(S\) 的 \(L\) 前缀和 \(S\) 的 \(L\) 后缀是循环相同的。
对于 \(100\%\) 数据,保证 \(1\le n\le 10^6\)。
题解
首先可以发现题目需要我们找到 \(S\) 的一种划分方式:\(ABTBA\),最大化 \(|A|+|B|\)。这个问题太难了。
尝试将正向的相等改为反向的相等,即尝试把 border 改成回文串。这两种结构之间有一种对称的关系,由于回文串的结构好,我们有时可以尝试将两个区间的比较改成判定回文串。
这里将 \(S\) 拆为前后两半 \(S_1S_2\),然后分为两个字符串 \(S_1, S_2^R\)(也就是 \(S_2\) 的反串),随后定义两个位置 \(i, j\) “相等”当且仅当 \(S_{1i}=S_{2j}\) 且 \(S_{1j}=S_{2i}\),回文串在此基础上定义。这样,我们就可以改成求两个回文串拼起来的长度了,也就是找 \(i, j\) 使得 \([1,i],[i+1,j]\) 都是回文的。
这里会尝试用 Manacher 维护,但是肯定会有一个问题:这样的“相等”关系不可能具有传递性,Manacher 还是正确的吗?你可以研究一下 Manacher 和它用到的性质并自己模拟一下,这里就不写过程了,就是对的,Manacher 不依赖相等关系的传递性。
那就很爽了,用 Manacher 求出每个位置为中心的最长回文半径,为了求答案我们可能需要维护一下以每个位置开始或结束的最长回文串长度,这是可以线性的,详见下文。然后就做完了,时间复杂度 \(O(n)\)。
事实上将 \(S\) 改写为 \(S_1S_nS_2S_{n-1}S_3S_{n-2}\cdots\) 也可以达到和上面一样的效果。
问题:如何求出以每个位置开始或结束的最长回文串长度?(P4555 [国家集训队] 最长双回文串 - 洛谷)
首先还是进行 Manacher 的插入隔板和算法流程求出最长回文半径 \(p_i\)。然后我们令 \(l_i,r_i\) 表示以 \(i\) 为左或右端点的最长回文串长度。根据我对 \(p\) 的定义(要额外 \(-1\)),得到 \(l_{i-p_i+1}\geq p_i-1,r_{i+p_i-1}\geq p_i-1\)。然后还有一个问题就是一个大回文串可以不断删掉两边的字符得到小回文串,所以还有 \(l_i\geq l_{i-2}-2, r_i\geq r_{i+2}-2\)。按顺序 chkmax 就可以了。代码见下文(代码里 \(llen\) 是 \(r\),\(rlen\) 是 \(l\))。
代码实现
#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, __VA_ARGS__)
#else
#define endl "\n"
#define debug(...) void(0)
#endif
using LL = long long;
constexpr int N = 1e6 + 10;
int n, pal[N], rlen[N], llen[N];
char a[N], b[N];
void manacher() {for (int i = 1, mid = 0, r = 0; i <= n; i++) {if (i <= r) pal[i] = min(pal[mid * 2 - i], r - i + 1);while (a[i - pal[i]] == b[i + pal[i]] && a[i + pal[i]] == b[i - pal[i]]) ++pal[i];if (i + pal[i] - 1 > r) r = i + pal[mid = i] - 1;}
}
int main() {
#ifndef LOCALcin.tie(nullptr)->sync_with_stdio(false);
#endifcin >> n;for (int i = 1; i <= n / 2; i++) {cin >> a[i * 2];a[i * 2 + 1] = '$';}if (n % 2 == 1) --n, cin >> a[0];for (int i = n / 2; i >= 1; i--) {cin >> b[i * 2];b[i * 2 + 1] = '$';}a[0] = '?', b[0] = '!', a[n + 2] = '*', b[n + 2] = '&', ++n, a[1] = b[1] = '$'; // 注意第一个字符前面也要插板debug("%s\n", a);debug("%s\n", b);manacher();debug(" "); for (int i = 1; i <= n; i++) debug("%d", pal[i]); debug("\n");for (int i = 1; i <= n; i++) rlen[i - pal[i] + 1] = max(rlen[i - pal[i] + 1], pal[i] - 1);for (int i = 3; i <= n; i += 2) rlen[i] = max(rlen[i], rlen[i - 2] - 2); // 这里 += 2 是因为只有这些地方有值for (int i = 1; i <= n; i++) llen[i + pal[i] - 1] = max(llen[i + pal[i] - 1], pal[i] - 1);for (int i = n - 2; i >= 1; i -= 2) llen[i] = max(llen[i], llen[i + 2] - 2);debug(" "); for (int i = 1; i <= n; i++) debug("%d", llen[i]); debug("\n");debug(" "); for (int i = 1; i <= n; i++) debug("%d", rlen[i]); debug("\n");int ans = 0;for (int i = 1; i <= n; i += 2) if (llen[i] == i / 2) ans = max(ans, llen[i] + rlen[i]);cout << ans << endl;return 0;
}