Manacher's Algorithm

迴文串就是有一箇中心,然後兩邊對稱。就像abcba、abba。在求一個串的迴文子串的時候,我們就需要枚舉每一箇中心,那麼就有奇偶迴文子串的區別了,長度是奇數的對稱中心是一個字符,長度是偶數的對稱中心是兩個字符之間。求最長迴文子串是一個典型問題,下面介紹一個O(n)的算法。

首先,對於奇偶長度的區別,這個算法的解決方法就是加入一個特殊的字符,插入每個字符之間,例如:#a#b#a#a#b#a#a#b#,這樣就可以枚舉到每一箇中心了,就是說把兩種情況都統一起來。算法的思想是利用前面已經求得的迴文子串的信息,來求以當前字符中心的迴文子串。設rad[i]代表以i爲中心,從s[i]開始算起,迴文串的半徑是多少,就是單邊延伸了多少個字符。現在我們要求i,有j的話,我們就先看看s[i]在不在以j爲中心的迴文子串(這個串設爲pj)裏面,其充要條件就是i越右邊越好)。

一、如果不在裏面的話,那麼就沒辦法利用s[i]在pj裏面的信息了,就只能窮舉來求rad[i]。

二、如果在裏面的話,那麼在pj中,就存在s[i]的鏡像s[i’] ,其中i’=j-(i-j)=2j-i ,我們就可以利用rad[i’]來求rad[i]了。那麼就有三種情況:

1、在邊界以內(不包括邊界),那麼就求得rad[i]=rad[i’],看下圖就明白爲什麼了

2、剛好在邊界上的話,就在邊界的下一個開始繼續比較下去

3、如果i+ rad[i’]-1超出了pj的範圍的話,那麼rad[i]=j+rad[j]-i ,因爲如下圖,1、13不在pj裏面,就是說s[1]!=s[13],而在p4裏面有s[1]=s[7],所以s[13]!=s[7],所以不能形成更長的迴文串。

還有個優化就是,我們當前求得的最長半徑是r的話,那麼我們知道當i超過一定的位置時,半徑最大也不會超過r,這個位置就是k=i+len-(r+i)=len-r。整段程序代碼如下:

int lps(char *s,int *rad)
{
     int len = (strlen(s)<<1)+2,i=2,r=1;
    int k=len-1,max=0,cen=0;//max代表最遠覆蓋到哪裏
    char str[MAXD]={‘$’,’#’};//第一個放$是爲了防止越界
    while(i<len)  {str[i++]=*s++; str[i++] = '#';}
    s = str;
    rad[0]=rad[1]=1;
    for(i=2; i<k; ++i) {
                 if(max > i)
                       rad[i] = rad[(cen<<1)-i]<max-i ? rad[(cen<<1)-i] : max-i;
                else
                       rad[i] = 1;
                while(s[i+rad[i]] == s[i-rad[i]]) ++rad[i];
                if(rad[i]>r) {r=rad[i];k=len-r+1;}
                if(rad[i]+i-1 > max) {
                             max = i+rad[i]-1;
                             cen = i;
                }
    }
    return r-1;
}

因爲每個字符都跟一個#配套,所以半徑就乘以2了,然後會多出一個#沒和字符配套,所以要-1,所以放回r-1就是最長的長度了.

附oj地址:http://acm.hust.edu.cn:8080/judge/problem/viewProblem.action?id=19933

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