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;
}
posted @ 2025-03-07 23:58  「癔症」  阅读(12)  评论(0)    收藏  举报