Manacher

一 、背景

1975年,Manacher发明了Manacher算法(中文名:马拉车算法),是一个可以在O(n)的复杂度中返回字符串s中最长回文子串长度的算法,十分巧妙。

让我们举个例子:
1.字符串:abbababa 最长回文子串:5(abbababa

2.字符串:abcbbabbc 最长回文子串:7(abcbbabbc

3.字符串:abccbaba 最长回文子串:6(abccbaba)

传统方法是,遍历每个字符,以该字符为中心向两边查找。时间复杂度为O(n^2),效率很差;
但是Manacher算法的时间复杂度可以达到O(n)!
Manacher(马拉车)-编程知识网
下面让我们看看它是怎么做的的吧Manacher(马拉车)-编程知识网

二、算法过程分析

回文分为奇回文(ababa)和偶回文(abba),这里比较难以处理,我们使用一个骚操作(划重点)。

我们将字符串首尾和每个字符间插入一个字符(注意:这个自符在串中并未出现)例如:’#’ s='abbadcacda’先转化成s_new=$#a#b#b#a#d#c#a#c#d#a#\0’(加粗的是边界)

这样原串中的偶回文(abba)与奇回文(adcacda),变成了(#a#d#d#a#)与(#a#d#c#a#c#d#a#)两个奇回文

定义数组p,用p[i]表示以i为中心的最长回文半径。再次举个例子
Manacher(马拉车)-编程知识网
(图片是借鉴了他人的)

Manacher(马拉车)-编程知识网

定义两个变量mx和id。mx就是以id为中心的最长回文右边界,也就是mx=id+p[id],随后我们需要mx做出它的最大贡献。

假设我们在求p[i](以i为中心的最长回文半径),如果i<mx(如上图),那么我们就用mx和j来更新到我们已知的可以更新的最大长度,代码如下:

if(i<mx)  p[i]=min(p[2*id-i],mx-i);

2*id-i是i关于id的对称点(上图j)(证明:i-id=id-j),而p[j]表示以j为中心的最长回文半径,这样我们就可以利用p[j]和mx加快速度了。

为什么要用p[j]和mx-i取min来更新呢?
首先我们想一下,p[j](以j为中心的最长回文半径)是已经知道了(因为是从前面扫过来的),若是p[j]>mx-i,我们是可以知道以j为中心,以mx的对称点到j的距离为半径形成的回文字符串是肯定存在的,并且id的左边直到mx的对称点与id的右边直到mx是对应的,所以mx是i目前可以更新到的最大回文半径;(还不明白的话就停下来画图好好想想)
若p[j]<mx-i,证明j的回文半径不到mx的对称点到j的距离,再次通过(id的左边直到mx的对称点与id的右边 直到mx是对应的),所以p[i]=p[j]。
但是取完min并不是最大的回文半径,接下来的就暴力搜就好了

代码

#include<bits/stdc++.h>
using namespace std;
char s[1100000];
char sn[1100000];
int a[1100000];
int ycl()
{int len=strlen(s);sn[0]='$';sn[1]='#';int sum=2;for(int i=0;i<=len;i++){sn[sum++]=s[i];sn[sum++]='#';}sn[sum]='\0';return sum;
}
int mlc()
{int cd=ycl();int mx=0,maxlen=-1,id;for(int i=1;i<=cd;i++){if(i<mx)a[i]=min(a[id*2-i],mx-i);else a[i]=1;while(sn[i-a[i]]==sn[a[i]+i])a[i]++;if(mx<a[i]+i){id=i;mx=a[i]+i;}maxlen=max(maxlen,a[i]-1);}return maxlen;
}
int main()
{scanf("%s",s);printf("%d",mlc());return 0;
}