问题
给出一个字符串,求出最长的回文串。
思路
朴素的想法:枚举字符串上的每一位,以其为回文串的中心进行扩展,统计答案。
这种方法是O(N^2)的,不优秀。
接下来考虑线性做法:
先将字符串中间插入特殊符号,以处理偶数长度的回文串。
对于每个回文串,我们可以给它记两个信息,即中心和半径r。再记录一个值mx代表当前扩展到的最右边界,以及这个回文串的中心p。
枚举每一个字符,分类讨论(以下j为i关于p的对称点):
(1)\(mx\leq i\),r[i]\(\geq\) 1(此时j无法给i提供信息)
(2)\(mx-i>r[j]\),r[i]=r[j](由于回文串的对称性可得)
(3)\(mx-i\leq r[j]\),r[i]\(\geq\)mx-i(由于[mx',mx]外的对称性无法满足,j给i提供了一个下界)
在初始的r[i]上再进行扩展即可,并更新mx和p。
由于扩展成功会导致mx右移,而mx右移不超过n次,故左右扩展总复杂度是O(N),枚举i也是O(N),故总时间复杂度为O(N)。
例题:
双倍回文
题意
记字符串x的倒置为\(x^R\),如果字符串s可以写成\(ww^rww^r\),则称它是一个双倍回文。
现在给出一个字符串,求出最长的双倍回文子串。
思路
在manacher中,考虑以当前枚举到的i为中心求双倍回文串。
当r[i]可以从r[j]推过来(情况2、3),我们就不需要判断这一部分的回文串,因为它与j处的回文串相同。
由此可知,一个字符串中本质不同的回文子串数量级是O(N)的
于是在每次mx扩展成功时再枚举。因为mx最多右移n次,所以这个复杂度也是O(N)的。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
const int N = 5e5 + 1;
int n, ans;
int hw[N << 1];
char a[N], s[N << 1];
int main() {
scanf("%d", &n);
scanf("%s", a);
s[0] = '@';
s[1] = '#';
for (int i = 0; i < n; i++) {
s[i * 2 + 2] = a[i];
s[i * 2 + 3] = '#';
}
n = n * 2 + 2;
s[n] = 0;
int mx = 0, mid;
for (int i = 1; i < n; i++) {
if (i < mx)
hw[i] = std::min(hw[mid * 2 - i], mx - i);
else
hw[i] = 1;
while (s[i + hw[i]] == s[i - hw[i]])
hw[i]++;
if (hw[i] + i > mx) {
if (i & 1)
for (int j = std::max(i + 4, mx); j <= i + hw[i]; j++) {
if (!(j - i & 3) && hw[i - (j - i) / 2] >= (j - i) / 2)
ans = std::max(ans, j - i);
}
mx = hw[i] + i;
mid = i;
}
}
printf("%d", ans);
}