【学习笔记】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;
}
posted @ 2025-09-02 16:32  GuoSN0410  阅读(12)  评论(0)    收藏  举报