最长回文字符串_Manacher算法_(O(n))
朴素算法求最长回文字符串包括奇数长的和偶数长的,求的时候都要分情况讨论,Manacher算法做了一个简单的处理,很巧妙地把奇数长度回文串与偶数长度回文串统一考虑,也就是在每个相邻的字符之间插入一个分隔符,串的首尾也要加,当然这个分隔符不能再原串中出现,一般可以用‘#’或者‘$’等字符。例如:
原串:abaabb
新串:#a#b#a#a#b#b#
这样一来,原来的奇数长度回文串还是奇数长度,偶数长度的也变成以‘#’为中心奇数回文串了。(读者可以自己写着试试,自己检验)
下面我们来讨论算法。
算法思路:把原串每个字符中间用一个串中没出现过的字符分隔开来(统一奇偶),用一个数组p[ i ]记录以 s[i]为中间字符的回文串向右能匹配的长度。先看个例子:(为了防止数组越界我在开头加了一个$,实际上新串就从下标1开始)(参考博客:http://blog.csdn.net/ywhorizen/article/details/6629268)
原串: a b a a b b
新串: $ # a # b # a # a # b # b #
0 1 2 3 4 5 6 7 8 9 10 11 12 13
p数组: 1 2 1 4 1 2 5 2 1 2 3 2 1
可以看出p[i]-1就是回文串的长度,下面予以证明:假设以i为中心的回文串长度为S,因为p[ i ]记录以 s[i]为中间字符的回文串向右能匹配的长度,所以有
S=2*p[ i ]-1;
又因为此时串中加了其他字符#,以s[i]为中心的回文串一定是以#开头和结尾的,以#为中间字符的就是长度为偶数的,以非#号为中间字符的就是长度为奇数的,例如“#b#b#”或“#b#a#b#”所以L 减去最前或者最后的‘#’字符就是原串中长度的2倍,即原串长度为(S-1)/2,化简的P[i]-1。
接下来就是求p数组了:
为了防止求P[i]向两边扩展时可能数组越界,我们需要在数组最前面和最后面加一个特殊字符,令P[0]=‘$’最后位置默认为‘\0’不需要特殊处理。此外,我们用mx 变量记录在求i 之前的回文串中,延伸至最右端的位置,同时用id 记录取这个mx 的id 值。通过下面这句话,算法避免了很多没必要的重复匹配。
if (mx > i){ p[i] = min(p[2*id - i], maxid - i); }
从左到右计算p[i]时 p[0.....i-1] 都以计算出,并且用一个变量mx记录 max{ k+p[ k ] } (k=0.....i-1),用id记录取最大值时的k, 则 p[ i ]= min( p[2*id - i ], mx - i )(这里需要好好思考,最好自己拿笔画画)
对于第一幅图以i为中间字符的回文串被以id为中间字符的回文串所覆盖,由对称性,p[ i ] = p[ 2*id - i ] 。对于第二幅图没有完全被覆盖,所以对于k>mx的字符,要一个一个匹配,才能确定p [ i ]。
下面给出代码:
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int maxn=2*1000005; char a[maxn],b[maxn]; int p[maxn]; int main() { int t,m; scanf("%d",&t); while(t--) { m=0; int ans=0; int mx=0,id=0; scanf("%s",a); int n=strlen(a); b[0]='$'; b[1]='#'; for(int i=0;i<n;i++) { b[2*i+2]=a[i]; b[2*i+3]='#'; } b[2*n+2]='\0'; int m=strlen(b); for(int i=1;i<m;i++) { if(mx>i) p[i]=min(p[2*id-i],mx-i); else p[i]=1; while(b[i+p[i]]==b[i-p[i]]) { p[i]++; } if(p[i]+i>mx) { mx=p[i]+i; id=i; } if(ans<p[i]) ans=p[i]; } printf("%d\n",ans-1); } }