作用
线性时间解决最长回文子串问题。
思想
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) O ( l e n ) 的呢?因为R不会减小,每次暴力处理的时候,p[i]增大多少,就说明R增大多少,而R最多增加len次。所以复杂度是 O(len) O ( l e n ) 的。
推论
(Manchery大神告诉我的,Orz)
2018.4.10UPD:原先“就出现了新的本质不同的回文子串”的说法是错误的,很抱歉误导了读者们QAQ。
一个串本质不同的回文子串个数最多为len个,证明方法和效率差不多:每次R增加的时候,就说明可能出现了新的本质不同的回文子串。
模板
同时水掉hihoCoder1032和HDU3068。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxl=1000000;int te,p[2*maxl+5];
char s[maxl+5],now[2*maxl+5];void readln() {scanf("%*[^\n]");getchar();}
bool Eoln(char ch) {return ch==10||ch==13||ch==EOF;}
int reads(char *x)
{int len=0;char ch=getchar();if (ch==EOF) return EOF;s[++len]=ch;while (!Eoln(s[len])) s[++len]=getchar();s[len--]=0;return len;
}
int Manacher(char *s)
{int len=strlen(s+1);for (int i=1;i<=len;i++) now[2*i-1]='%',now[2*i]=s[i];now[len=len*2+1]='%';int pos=0,R=0;for (int i=1;i<=len;i++){if (i<R) p[i]=min(p[2*pos-i],R-i); else p[i]=1;while (1<=i-p[i]&&i+p[i]<=len&&now[i-p[i]]==now[i+p[i]]) p[i]++;if (i+p[i]>R) {pos=i;R=i+p[i];}}int MAX=0;for (int i=1;i<=len;i++) MAX=max(MAX,p[i]-1);return MAX;
}
int main()
{freopen("Manacher.in","r",stdin);freopen("Manacher.out","w",stdout);scanf("%d",&te);readln();while (te--) reads(s),printf("%d\n",Manacher(s));return 0;
}