Manacher 算法
直切正题,这个算法基本就是用 \(O(n)\) 的复杂度查一个字符串内的最长回文。
这个东西其实和 KMP 有很多相似的地方,如果已经对 KMP 有了解的同学对这个肯定能起手的快些。
正篇
首先,明确一个数组的含义
\(d_i\) 表示以 i 为中心,向两边扩展回文的半径。
这个东西是包括 i 本身的(其实也可以不包括,看喜好)。
然后是一些预处理,由于你回文串可以是偶串,为了方便咱们在每两个字符间加一个占位符。
我们考虑已知 \(i\) 前面的所有 \(d\) 值,那么如何递推呢?
首先是要用一个回文串包含 \(i\) 的位置来更新对罢,这个串的左右端点计为 \(l\),\(r\) 。这样就相当于一个大回文串两端套了两个一样的小回文串,用 \(i\) 的对称点来更新即可。当然要注意,这个长度上限就是 \(r - i + 1\) , 再多就会超出大回文串的包含范围,咱们就不知道是否还相等了,这时候需要暴力判一下。
那么如果正常枚举到一个 \(i\) 已经超出了大回文串的管辖范围,就需要更新一下。那么就近选择 \(l = i - d[i] + 1\) , \(r = i + d[i] - 1\) 。
其实这个东西光看文字真的很难理解,可以去视频平台看一些带图的题解。
其实整个就是分类讨论一下即可,代码难度实现不高,但是思维含量还是高的。
可以证明复杂度为 \(O(n)\) , 因为你的 \(r\) 是一直跳动着往右移的 , 你的暴力枚举相当于是一步一步追着 \(r\) 一起在向右移,因此一共最多就移了 \(n\) 次。
初学的话还是放一下代码罢。
#include <bits/stdc++.h>
#define ri register int
#define N 30000007
using namespace std;
int cnt , n , ans , d[N];
char s1[N] , s[N];
namespace get_d
{
int _() {
d[1] = ans = 1;
for(ri i = 2 ,l ,r = 1; i <= n; ++i) {
int sym = l + r - i; //对称点
if(i <= r) {
d[i] = min(d[sym] , r - i + 1); //直接用大回文串对称过去
}
while(s[i - d[i]] == s[i + d[i]]) {
++ d[i]; //暴力
}
if(i + d[i] - 1 > r) {
l = i - d[i] + 1 , r = i + d[i] - 1; //大回文串管不了,要换新的
}
ans = max(ans , d[i]);
}
return ans - 1; //由于咱们塞了一堆占位符,所以原来的回文直径就等于回文半径减去多加的那个中间字符
}
}
int main()
{
scanf("%s" , s1 + 1);
n = strlen(s1 + 1);
s[0] = '#' , s[++cnt] = '$';
for(ri i = 1; i <= n; ++i) {
s[++cnt] = s1[i] , s[++cnt] = '$';
}
n = cnt ;
cout << get_d :: _();
return 0;
}