最長迴文字符串_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 )(這裏需要好好思考,最好自己拿筆畫畫)

aa

      對於第一幅圖以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);
    }
}





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章