学习NOTE 5——Manacher(马拉车)

前情提要

这其实是我在没学马拉车,只看题解的前提下写出来的依托答辩

正题

下面进入正题。咳咳

是什么

马拉车?不就是马拉着车吗? 其实就是Manacher的音译。
Manacher算法是字符串的算法之一,是用于计算一个字符串里的最长回文串长度的算法。
example: aaa的最长回文串长度为 \(3\),abcbac的长度为 \(5\)

怎么实现

想要计算,就得先从例子入手来分析:
例:

  • ababa:最长回文串长度为\(5\),中心为第三个a,下标为 \(3\)(以下所有字符串下标均从\(1\)开始,包括代码);
  • abccba:最长回文串长度为\(6\),中心为两个c中间,下标为 ?

不对!
为什么偶数长度的回文字符串的回文中心在字符中间?而不像奇数长度的一样,在字符上。这不好表示回文中心啊。

所以我们可以把字符串变为奇数长度的!

至于怎么变呢?在每一个字符两边加#
比如:

  • abccba变为#a#b#c#c#b#a#
  • ababa变为#a#b#a#b#a#

这样既让偶数长度的变成了奇数的,又不影响字符串的回文性。好方法!!


接下来我们令\(p_i\)记录:以当前第\(i\)为回文中心是最长回文“子”串长度。

考虑当前需要求出 \(p_i\)。我们可以记录一个 \(r\),用来表示在所有 \(1 \le j < i\)\(j\) 中,以 \(j\) 为中心点的回文串的右端点中,最右的那一个。记这个 \(r\) 为右端点的回文串的中心点为 \(j'\)。此时我们需要进行分类讨论:

1. \(r>i\)

此时我们可以得到下图:

这个图是什么意思?我们首先可以找到 \(i\)\(j'\) 为对称中心的对称点 \(i'=j'\times2-i\)

注:\((i+i')/2=j'\rightarrow i'=j'\times2-i\)

然后,因为这一整个是一个回文串,所以,\(i'\) 下面那一段(指那一段子串)和 \(i\) 下面那一段一定也相同。所以在这种情况下,\(p_i=p_{i'}\),可以直接得出!

但是!情况不一定总是这么好。在 \(r>i\) 的情况中,还有另外一种情况:

如图,当 \(p_{i'}\) 很大的时候,这一段可能超出了整个大回文串,当这个子串对称到以 \(i\) 为中心的这边时,右端点超过了 \(r\),而我们并不清楚超出的部分。
在这种情况下,我们不能保证 \(i\) 的回文长度一定有这么长,只能保证从 \(i\)\(r\) 这一段一定回文。所以,我们可以先 \(p_i\leftarrow r-i\),然后计算 \(p_i\) 的最终值。

这样,\(r>i\) 的情况暂时被我们解决了。

在每次更新完 \(p_i\) 后,注意要实时更新 \(r\)\(j'\),用来计算后面的答案。


2.\(r<i\)

这是就不一样了,看图:

可以看出,没有任何可以使用的信息,所以只能计算 \(p_i\) 了。此时也需要更新 \(r\)

遍历 \(1 \sim n\) 重复如上过程,即可得到所有 \(p_i\)。因为在计算之前在字符串中还插入了一些 #,所以最终答案其实就是: $$\max_{1 \sim n} (p_i-1)$$。

代码

PID:P3805
终于到代码啦~
其实代码很短~ (不要剧透)

#include<bits/stdc++.h> 
#define int long long 
using namespace std;
const int N=22*1e6+10;
int p[N],ans,j,r;
//p[i]表示以i为中心的回文半径(包括中心点本身)
string s;
char c; 
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	while(cin>>c) s+=c,s+="#";//将偶数长度变为奇数长度
	s=" #"+s;//开头还没加
	for(int i=1;i<=s.size();i++){
		// 核心:若i在当前最右回文边界r内,则利用对称性初始化p[i]
		// 否则p[i]从1开始(单个字符一定是回文)
		p[i]=r>i?min(r-i,p[j*2-i]):1;
		//            ↑      ↑
		//     不能超出边界  对称点的回文半径
		while(s[i-p[i]]==s[i+p[i]]) p[i]++;//尝试扩大回文半径
		//如果当前回文右边界超过了之前的,更新中心和右边界
		if(p[i]+i>r){
			j=i;
			r=i+p[i];
		}
	}
	for(int i=1;i<=s.size();i++) ans=max(ans,p[i]-1);//求最大值
	//因为之前加了#,故要-1
	cout<<ans;
	return 0 ;
}


注: 部分内容参考:Eason_cyx的题解,感谢dalao!!

posted @ 2026-07-27 19:36  _T_M_T  阅读(6)  评论(0)    收藏  举报