manacher 算法总结

测试一下这个博客园的功能(图片好像只能在洛谷上看,有时间就改)

manacher 算法总结

题目大意

给定一字符串,求其最长回文串长度

方法对比

暴力效率:O(n3),优化后为O(n2)

manacher效率:O(n)

算法思想

回文串有两种:奇回文与偶回文

分类讨论太麻烦,主要是我不会,于是我们就统一为奇回文

如何统一

例:

abbab

偶回文:abba 奇数回文:bab

我们在中间各插入一个符号,例如#号

则原式变为:

#a#b#b#a#b#

此时奇回文还是已某个子母为中心的回文,如:

bab->#b#a#b#

此时这个回文仍以a为中心

通过观察可以发现,偶回文变成已#为中心的奇回文,如:

abba->#a#b#b#a#

此时这个回文已第三个#号为中心

我们把原先的串命名为 s 串,修改后的串命名为 t 串,定义数组 p

p[i] 表示在 t 串中以 i 为中心最多能扩散的半径(包括 i 本身)

例如:

t: # a # b # b # a # b #
p: 1 2 1 2 5 2 1 4 1 2 1

可是此时 ts 不同,怎么通过 p 来还原长度呢?

还原长度

先上结论:最长回文长度 ans

ans=MAXni1(p[i]1)

为什么是 p[i]1 呢?

如果是奇回文:

#b#c...#b#a#b...c#b#

右边每一个字母都可以与对应左边字母的右边#消除

最后剩下最左边的#,所以为 p[i]1

如果是偶回文:

#b#c...d#a#a#a#d...c#b#

按照奇回文的思路我们也可以得到 p[i]1 这个结论,这里可以再自己手动推一下

现在大致思路是这样,可是怎么求 p[i] 呢,这是manacher精粹之一

对称思维

我们首先假设我们进行到 p[i], 且 p[1...i1] 已知

则前面 p[1...i1] 必存在一个 p[id],使得 p[id]+id 最大,记为 mx

即:mx=MAXi1id=1(p[id]+id)

注意,id 可以为 1i1 任意一位置

图中黑色矩形为 t 串,蓝色矩形代表子母 i,红色矩形代表子母 id,红色框架代表已 id 为中心的最长回文串,其中右边界记为 mx

同时,以 id 为原点两边会出现对称情况,所以 i 必然会出现其对称点 i

图中绿色矩形代表与蓝色矩形 t[i] 相等且围绕 id 对称的子母 t[i], 黄色线代表两点对称

此处直接给出i'的公式,有时间可以自己证

i=2×idi

具体是运用了原点对称的思想

此时 i 也必然有自己的管辖范围(即以 i 为中心的最长回文长度),我们分两种情况讨论

情况1:i 管辖范围在 id 的管辖范围以内

则:p[i]=p[i]

因为此时 i 的管辖范围必然与i的管辖范围以 id 为原点一一对称,详见图:

图中绿色框架代表 i 管辖范围,蓝色框架代表 i 管辖范围,下面的黄色线代表绿色框架与蓝色框架以 id 为中心的一一对应

情况2:i 管辖范围不在 id 管辖范围以内

则首先,p[i] 至少为 p[i]=mxi

那么超出部分(紫色部分)怎么办呢,暴力呗

图中紫色区域代表超出部分

此时我们就可以处理所有 p[i]

边界问题

可以看出,我们再求 2×idimxi 时可能越出左边界

求紫色区域时可能越出右边界,则我们在 t 串左右分别加上两个字符,例如+和-

核心思路大致如此

核心代码

	for(int i=1;i<=n;++i)
	{
		p[i]=1;
		if(i<=mx)
			p[i]=min(p[2*id-i],mx-i);
		while(t[i-p[i]]==t[i+p[i]])
			++p[i];
		if(i+p[i]>mx)
			id=i,mx=i+p[i];
	}

效率分析

双重循环,看起来是O(n2),但事实上每次进入while循环,mx 值必改变,而 mx 值最大为 n,所以时间效率为O(n)(不考虑常数)

完整代码

#include<bits/stdc++.h>
using namespace std;
char s[11000010];
char t[25000010];
int p[25000010];
int l,mx,id,ans;

int main()
{
//	freopen("tiaoshi.in","r",stdin);
//	freopen("tiaoshi.out","w",stdout);
	scanf("%s",s+1);
	t[0]='+';
	l=1;
	for(int i=1;s[i];++i,++l)
	{
		t[l]='#';
		t[++l]=s[i];
	}
	t[l]='#';
	t[l+1]='-';
	for(int i=1;i<=l;++i)
	{
//		printf("%c",t[i]);
		p[i]=1;
		if(i<=mx)
			p[i]=min(p[2*id-i],mx-i);
		while(t[i-p[i]]==t[i+p[i]])
			++p[i];
		if(i+p[i]>mx)
			id=i,mx=i+p[i];
		ans=max(ans,p[i]);
	}
//	printf("\n");
	printf("%d",ans-1);
	return 0;
}


参考代码2:

scanf("%s", c+1); n=strlen(c+1); 
s[0]='-'; s[1]='#'; 
for(i=1, j=2; i<=n; ++i, ++j) 
	s[j]=c[i], s[++j]='#'; 
s[j]='+'; n=j-1; r=0; 
for(i=1; i<=n; ++i)
{
	if(i>r) a[i]=2; 
	else a[i]=min(a[2*j-i], r-i+1); 
	while(s[i-a[i]]==s[i+a[i]]) ++a[i]; 
	if(i+a[i]-1>r) r=i+a[i]-1, j=i; 
	ans=max(ans, a[i]-1); 
}
printf("%d", ans); 
posted @   zhangtingxi  阅读(78)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示