1. 程式人生 > >「Manacher演算法」學習筆記

「Manacher演算法」學習筆記

覺得這篇文章寫得特別勁,插圖非常便於理解。

目的:求字串中的最長迴文子串。


演算法思想

考慮維護一個數組$r[i]$代表迴文半徑。迴文半徑的定義為:對於一個以$i$為迴文中心的奇數迴文子串,設其為閉區間$[L,R]$,則半徑$r=R-i+1$。

$Manacher$演算法利用一個類似$DP$的方法來求解這個問題。考慮維護一個目前已經達到的最大的右邊界$P$,此右邊界對應的對稱中心以及左邊界分別為$pos$,$P'$。那麼分類討論:

1. $i<P$

此時我們可以找到$i$關於$pos$的對稱點$j$。由於$[P',P]$是迴文的,所以如果$j$的迴文子串不超過邊界,那麼有$r[i]=r[j]$。

如果$j$超過邊界了,說明至少區間內的那一段是能夠滿足的。因此$r[i] \geq P-i+1$。對於超出的部分,暴力比較(同時更新P)

2. $i \geq P$

此時根據前面的來轉移已經沒有意義了。直接暴力。

綜上我們已經可以求解出所有的$r[i]$了。那麼前面所說的迴文中心一定要是奇數的長度。能不能把偶數轉化為奇數?

答案是在任意兩個相鄰字元之間插入特殊符號進行間隔(包括開頭和結尾)。這樣就一定是奇數了。易知對於這種情況,對稱中心$i$對應的迴文串的長度也就是$r[i]-1$。


剛才是用數學角度在討論問題,如果程式碼也按照分類討論這個標準來實現未免有些冗長。考慮簡化問題。

既然$i \geq P$和$j$出界的情況都需要暴力匹配,不如合併到一起?我們發現只要我們確定$r[i]$的最小取值,然後不停增大$r[i]$判斷是否成立就好了。當$i \geq P$時,由於未知,最小值固然是1. 而對於$j$出界,最小值固然是$P-i+1$。因此問題就很簡單了


$code$

inline void Manacher(){
    int j=0,P=0,pos=0,p=0,N=0,n=strlen(t+1);
    for(int i = 1; i <= n; ++i){
        s[++N] = '$';
        s[++N] = t[i];
    }
    s[
++N] = '$'; for(int i = 1; i < N; ++i){ if(P > i) r[i] = Min(r[pos-(i-pos)], P-i+1); else r[i] = 1; while(s[i-r[i]] == s[i+r[i]] && i-r[i]>=1 && i+r[i]<=N) ++r[i]; if(i+r[i]-1 > P) P = i+r[i]-1, pos = i; ans = Max(ans, r[i]-1); } }