Manacher 学习笔记

Manacher是一个求出一个字符串中所有回文子串的利器。

记录方法

首先我们发现一个问题,一个长为 S 的字符串一共有 S2 个子串,所以记录回文子串时不可能记录左右端点。如何解决呢?根据回文串的特点,我们发现,一个回文串,将它的两端各删去一个字符,那么它还是一个回文串。所以我们可以记录下每一个回文串的中心点以及回文串的“半径” ai ,这样记录的问题解决了,接下来就到如何快速求解了。

过程

在Manacher中,我们需要维护一个 lr ,表示当前右端点最靠右的回文串是哪个。设当前枚举到的中心点为 i ,之前的所有点的 a 已计算完毕,分两种情况讨论:

i>r

此时我们不能从前面计算到的数据求出当前点的 ai ,只能暴力扩展半径到不能再扩展为止。

ir

此时我们可以从当前的这个回文串中找到 i 的对应点 j=l+(ri) ,之后我们在大部分情况下可以将 aj 赋值 ai。但是考虑到可能 jajl 小,不能保证 r ~ i+ail ~ jaj (这个是倒过来的)相同,所以在赋值时将 ajrimin ,之后再暴力扩展。

计算完一个点的 a 后,要记得更新 lr

初始值可以设为 l=0,r=1

技巧

等等,上面讲的好像是只能算奇数长度的回文子串,那偶数的怎么办?

其实用上面的方法再推一推就可以找到计算偶数的方法了。但是有另一个技巧,可以在原字符串中,每两个字符中间插入一个不在原字符集中的字符(例如‘#’),这样就可以一遍算出所有的回文子串。

如果怕在枚举时边界不好控制,也可以在字符串两端加不一样的特殊字符。

Code

#include<cstring>
using namespace std;
const int N=1.1e7+10;
char ch[N*2];int a[N*2];
int max(int x,int y)
{
if(x>y)
return x;
return y;
}
int min(int x,int y)
{
if(x<y)
return x;
return y;
}
int main()
{
int i,n,l=0,r=-1,ans=0;
scanf("%s",ch);
n=strlen(ch);
for(i=n;i>=1;i--)
ch[i*2]=ch[i-1];
for(i=1;i<=n*2+1;i+=2)
ch[i]='#';
ch[0]='!';ch[n*2+2]='@';
for(i=1;i<=2*n+1;i++)
{
if(i<=r)
a[i]=min(r-i,a[l+r-i]);
while(ch[i-a[i]-1]==ch[i+a[i]+1])
a[i]++;
if(r<a[i]+i)
{
r=a[i]+i;l=i-a[i];
}
ans=max(ans,a[i]);
}
printf("%d",ans);
}
posted @   Cyan_wind  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示