【学习笔记】manacher 算法
简介
manacher 算法,也称马拉车算法,可以在 \(O(n)\) 时间内,计算出字符串的极长回文子串。
实现
首先发现对于长度为偶数的回文串,它的回文中心可能在两个字符之间,所以为了方便,在每两个相邻字符之间添加一个 # 将所有回文串的长度转化为奇数。
记录几个变量:
- \(x\):表示遍历到当前位置,所有回文串的最右端。
- \(id\):遍历到当前位置,最长回文串的中心坐标。
- \(p_i\):以 \(i\) 为中心的最长回文半径(包含字符 \(i\))。
考虑算法的执行过程:
假设现在算到了第 \(i\) 个字符,若 \(i\le mx\),则由回文串的对称性,可以将 \(p[i]\) 初始化为 \(\min(p_{2 * id - i},mx - i + 1)\)(即与 \(i\) 以 \(id\) 为中心的对称点的最长回文半径,但不能超过 \(mx\),因为超过的部分对称性无法保证);否则就只能将 \(p[i]\) 初始化为 \(1\)(只包含当前这一个字符)。
将 \(p[i]\) 初始化完后,暴力扩展并更新三个变量就可以了。
最后答案就是 \(\max(p_i - 1)\)(需要扣掉 #)
时间复杂度证明:
显然对于第 \(i\) 个字符,在暴力扩展前若 \(i + p_i - 1 < mx\),它必然不会暴力扩展(中间有一个字符把它挡住了),所以若暴力扩展成功,则必然会将 \(mx\) 右移,而 \(mx\) 最多右移 \(O(n)\) 此所以最多暴力扩展 \(O(n)\) 次,得证。
性质延申:有 manacher 算法可知,大部分情况下以 \(i\) 为中心的回文串都是直接继承得来的,只有当 \(mx\) 右移时才会形成新的会文串,所以一个串的本质不同回文串最多 \(O(n)\) 个。
code
注意,最好在字符串的首尾都加一个字符集中不会出现且不同的字符,以防匹配越界。
#include<bits/stdc++.h>
#define N 22000010
#define fo(a, b, c) for(int b = a; b <= c; b++)
using namespace std;
int n, id, mx, p[N], ans = 0;
char a[N], ch[N];
int main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
ch[0] = '~';
cin >> a + 1, n = strlen(a + 1);
fo(1, i, n) ch[2 * i] = a[i];
fo(1, i, n + 1) ch[2 * i - 1] = '#';
n = n * 2 + 1;
mx = 0, id = 0;
fo(1, i, n){
if(i <= mx) p[i] = min(p[id * 2 - i], mx - i + 1);
else p[i] = 1;
while(ch[i + p[i]] == ch[i - p[i]]) ++p[i];
if(p[i] + i - 1 > mx) mx = i + p[i] - 1, id = i;
ans = max(ans, p[i] - 1);
}
cout << ans;
return 0;
}

浙公网安备 33010602011771号