转自:http://blog.csdn.net/zzkksunboy/article/details/72600679
作用
线性时间解决最长回文子串问题。
思想
Manacher充分利用了回文的性质,从而达到线性时间。
首先先加一个小优化,就是在每两个字符(包括头尾)之间加没出现的字符(如%),这样所有字符串长度就都是奇数了,方便了很多。 abcde->%a%b%c%d%e%
记录p[i]表示i能向两边推(包括i)的最大距离,如果能求出p,则答案就是max(p)-1了(以i为中点的最长回文为2*p[i]-1,但这是加过字符后的答案,把加进去的字符干掉,最长回文就是p[i]-1)。
我们假设p[1~i-1]已经求好了,现在要求p[i]:
假设当前能达到的最右边为R,对应的中点为pos,j是i的对称点。
1.当i<R时
由于L~R是回文,所以p[i]=p[j](i的最长回文和j的最长回文相同)。
这种情况是另一种:j的最长回文跳出L了。那么i的最长回文就不一定是j的最长回文了,但蓝色的肯定还是满足的。
综上所述,p[i]=min(p[2*pos-i],R-i)。
2.当i>=R时
由于后面是未知的,于是只能暴力处理了。
效率
但是这样看起来很暴力,为什么复杂度是O(len)的呢?因为R不会减小,每次暴力处理的时候,p[i]增大多少,就说明R增大多少,而R最多增加len次。所以复杂度是O(len)的。
推论
(Manchery大神告诉我的,Orz)
一个串本质不同的回文子串个数最多为len个,证明方法和效率差不多:每次p[i]增加的时候,就说明出现了新的本质不同的回文子串。
代码:
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 using namespace std; 6 const int N=1e6+5; 7 8 int len1,len2; //len1为s长度,len2为str长度 9 int p[N]; //p[i]为点i处的最长回文半径 10 char s[N],str[N]; //s为原字符串,str为扩充后的字符串 11 12 void init(){ 13 str[0]=‘$‘; 14 str[1]=‘#‘; 15 for(int i=0;i<len1;i++){ 16 str[i*2+2]=s[i]; 17 str[i*2+3]=‘#‘; 18 } 19 len2=len1*2+2; 20 str[len2]=‘%‘; 21 } 22 23 void manacher(){ 24 int id=0,mx=0; //mx记录回文串延伸的最远位置,id则为对应mx的点,mx=p[id]+id; 25 for(int i=1;i<len2;i++){ 26 if(mx>i) p[i]=min(p[2*id-i],mx-i); //点i在mx之内 27 else p[i]=1; //在mx之外则p[i]直接初始化为1 28 while(str[i+p[i]]==str[i-p[i]]) //暴力匹配 29 p[i]++; 30 if(p[i]+i>mx){ //更新mx,id 31 mx=p[i]+i; 32 id=i; 33 } 34 } 35 }