樸素算法求最長迴文字符串包括奇數長的和偶數長的,求的時候都要分情況討論,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);
}
}