马拉车算法 Manacher‘s Algorithm 是用来查找一个字符串的最长回文子串的线性方法,由一个叫
Manacher 的人在 1975 年发明的,这个方法的最大贡献是在于将时间复杂度提升到了线性
dp[i] = ma > i ? min(dp[2 * mod - i], ma - i) : 1;
可以这么说,这行要是理解了,那么马拉车算法基本上就没啥问题了,那么这一行代码拆开来看就是
如果 ma > i, 则 p[i] = min( dp[2 *mod - i] , ma - i )
否则,dp[i] = 1(本身)
【care一】ma>i
(1)当 ma - i >dP[j] 的时候,以S[j]为中心的回文子串包含在以 S[mod] 为中心的回文子串中,由于 i 和 j 对称,以 S[i] 为中心的
回文子串必然包含在以 S[mod] 为中心的回文子串中,所以必有 P[i] = P[j],其中 j = 2*id - i,因为 j 到 id 之间到距离等于
id 到 i 之间到距离,为 i - id,所以 j = id - (i - id) = 2*id - i,参见下图。ma的对称点 =2*mod-i(i关于mod的对称点) (mod为能延伸到最右端的位置的那个回文子串的中心点位置)mi j mod i ma(记录向右边延伸最多的位置)
—————|—————————|———————————————|———————————————|—————————|——|————————|————————————— ———————|—————————|| dp[j] |(以dp[j]表示以v[j]字符为中心的回文子串的半径)—————————————————————————————————————————————————————————————(2)当 dP[j] >= ma - i 的时候,以 S[j] 为中心的回文子串不一定完全包含于以 S[mod] 为中心的回文子串中,但是基于对称性可知,
下图中两个绿框所包围的部分是相同的,也就是说以 S[i] 为中心的回文子串,其向右至少会扩张到 ma的位置,也就是说 dP[i] = ma - i。
至于 ma 之后的部分是否对称,就只能老老实实去匹配了,这就是后面紧跟到 while 循环的作用。ma的对称点 =2*mod-i(i关于mod的对称点) (mod为能延伸到最右端的位置的那个回文子串的中心点位置)mi j mod i ma(记录向右边延伸最多的位置)
—————|—————————|———————————————|———————————————|———————————|————————||——————————|—————————| |——————————|———————————|——|| dp[j] | |(以dp[j]表示以v[j]字符为中心的回文子串的半径)|—————————————————————————————————————————————————————|【care二】
对于 ma <= i 的情况,无法对 P[i] 做更多的假设,只能 P[i] = 1,然后再去匹配了。
题目:
给出一个只由小写英文字符a,b,c...y,z组成的字符串S,求S中最长回文串的长度.
回文就是正反读都是一样的字符串,如aba, abba等
Input
输入有多组case,不超过120组,每组输入为一行小写英文字符a,b,c...y,z组成的字符串S
两组case之间由空行隔开(该空行不用处理)
字符串长度len <= 110000
Output
每一行一个整数x,对应一组case,表示该组case的字符串中所包含的最长回文长度.
Sample Input
aaaaabab
Sample Output
4
3
AC代码
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int M=110010;
int dp[M*2];//dp[i]表示以v[i]字符为中心的回文子串的半径
char u[M],s[M*2];
int ans,len;
void manacher()
{/**马拉车算法的第一步是预处理,做法是在每一个字符的左右都加上一个特殊字符,比如加上 '#',这样做的好处是不论原字符串是奇数还是偶数个,处理之后得到的字符串的个数都是奇数个*/int l=0;s[l++]='$'; //s[0]='*',s[len*2+2]='\0',防止在while时dp[i]越界s[l++]='#';//Manacher处理回文串时,需要在每个字符串的中间以及两端插入不会出现的字符,然后再枚举for(int i=0; i<len; i++) //插入了len+1个'#',最终的s长度是1~(len+len+1)即2*len+1,首尾s[0]和s[2*len+2]要插入不同的字符{s[l++]=u[i];s[l++]='#';}s[l]='\0';int ma=0,mod=0;///ma记录向右边延伸最多的位置,for(int i=0; i<l; i++){dp[i]=ma>i?min(dp[mod*2-i],ma-i):1;/**i为我们设定的回文串中心,若>ma,则此时没有回文串被包含,只能慢慢枚举,其他详细分析,见上*/while(s[dp[i]+i]==s[i-dp[i]]) dp[i]++;if(i+dp[i]>ma){ma=i+dp[i];mod=i;}}
}
int main()
{while(~scanf("%s",u)){len=strlen(u);manacher();ans=-1;for(int i=0; i<2*len+2; i++)ans=max(ans,dp[i]-1);printf("%d\n",ans);}return 0;
}