给出一个字符串,求出最长的回文串。
朴素的想法:枚举字符串上的每一位,以其为回文串的中心进行扩展,统计答案。
这种方法是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); }