【学习笔记】manacher

众所周知,manacher 又叫“马拉车”算法,是一种线性求解最长回文子串的算法。

推荐结合模板阅读此文。

求最长回文子串,首先想到的是暴力。枚举子串的左右端点 l,r ,再判断这个子串是否回文。总复杂度 O(n3),效率过低。

观察发现,我们可以只枚举中点,然后同时向左右不断扩展,当无法再扩展时更新答案。

具体来说,我们求出所有的 Pi,即以字符 Si 为中心的最大扩展长度(包括 Si 本身)。一开始,所有 Pi 设为 1(单个字符肯定是回文串)。

但这样有一个问题:长度为偶数的回文串没有回文中心。针对这个问题,我们在字符串每个字符间加上一个“特殊字符”,保证这个字符不在原字符串的字符集中(下文取 | 作为这个特殊字符)。

拿样例 aaa 举例,加上特殊字符后整个字符串变成了这个样子:

|a|a|a|

这有什么好处呢?对于回文长度为偶数的子串,回文中心就为一个 |

所以整个串的 P 数组如下:

S: |  a  |  a  |  a  |
P: 1  2  3  4  3  2  1

可以推出,以 Si 为回文中心在新字符串的最长回文串的长度为 Pi×21。去除特殊字符(显然特殊字符比原字符多一个,所以特殊字符的个数为 Pi),那么最长回文子串的长度即为 Pi1

所以答案为 max{Pi1}

#include<bits/stdc++.h>
using namespace std; 
const int N = 11e6 + 9;
char s[N << 1];
int p[N << 1],len,ans;
void read(){//读入字符串
	char c = getchar();len = 1;
	s[len] = '|';
	while(c < 'a' || c > 'z')
		c = getchar();
	while(c >= 'a' && c <= 'z'){
		s[++len] = c;
		s[++len] = '|';//添加特殊字符
		c = getchar();
	}
}
int main(){
	read();
	for(int i = 1,mx = 0,id = 0;i <= len;i++){
		p[i] = 1;
		while(s[i - p[i]] == s[i + p[i]])//暴力扩展
			p[i]++;
	    ans = max(ans,p[i] - 1);//更新答案
	}
	printf("%d",ans);
	return 0;
}

但是这样还不是最终的 manacher 算法,我们如果想要得到一个线性算法,就要想如何线性求出 Pi

我们记在分别以前 i 个字符为回文中心扩展后访问到的最右侧下标mx,即 mx=max{i+Pi1}(以 i 为扩展中心扩展的长度Pi,那么能访问到的最右侧就是 i+Pi1),同时记取到最大值的 iid(如果有多个取最右边的一个)。那么在扩展下一个字符 si+1 时,如果 i+1mx,则 si+1 一定被包含在以 id 为中心的最长回文串中,那么 si+1 一定在这个回文串中有对称的字符。

接下来我们推一下以 id 为对称中心,i(不是 i+1)的对称点的下标:

设这个下标为 j,则 jid 的距离和 idi 的距离应该相等,即 idj+1=iid+1,整理即得 j=2×idi

所以如果对称点 j 有一些回文串包含在以 id 为回文中心的最长回文串中,则其中最大者的长度为 min{Pj,idj}(记为 len),易知 len 等于 i 到这个回文串右边界 mx 的长度(由对称可知),所以 len=min{Pj,mxi+1}这是为了保证以 j 为回文中心的这个串一定在以 id 为中心的最长回文串中,这样对称过去才能保证i 为回文中心,上面这个数值扩展长度一定是一个回文串。

由上述我们可以得到如果 i 包含在以 id 为中心的最长回文串中,则 Pi 至少为 min{Pj,mxi+1},我们再以这个基础暴力扩展。否则,我们就只能老老实实的从 Pi=1 开始扩展。

扩展完成之后,我们再按 mxid 的定义更新 mxid 即可。

总复杂度是线性的(不想证了)。

#include<bits/stdc++.h>
using namespace std; 
const int N = 11e6 + 9;
char s[N << 1];
int p[N << 1],len,ans;
void read(){
	char c = getchar();len = 1;
    s[0] = '~';s[len] = '|';
	while(c < 'a' || c > 'z')
		c = getchar();
	while(c >= 'a' && c <= 'z'){
		s[++len] = c;
		s[++len] = '|';//添加特殊字符
		c = getchar();
	}
}
int main(){
	read();
	for(int i = 1,mx = 0,id = 0;i <= len;i++){
		p[i] = i <= mx ? min(p[(id << 1) - i/*i关于id的对称点*/],mx - i + 1) : 1;
		while(s[i - p[i]] == s[i + p[i]])//暴力扩展
			++p[i];
	    if(p[i] + i - 1 >= mx){//访问到的最右侧的下标大于mx,更新mx和id
			mx = p[i] + i - 1;
			id = i;
		}
	    ans = max(ans,p[i] - 1);//更新答案
	}
	printf("%d",ans);
	return 0;
}
posted @   5t0_0r2  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示