Manacher算法詳解


##問題 什么是回文串,如果一個字符串正着度讀和反着讀是一樣的,這個字符串就被稱為回文串。 such as

noon level aaa bbb

既然有了回文,那就要有關於回文的問題,於是就有了—— 最長回文子串:給定一個字符串,求它的最長回文子串長度。

##暴力 找出所有的子串,遍歷每個子串判斷他們是否為回文串。 時間復雜度$O(n3)$ ###優化 因為回文串是對稱的,根據這個性質,枚舉每個位置,找在這個位置上能擴展到的最長回文串。 時間復雜度$O(n2)$ ##Manacher算法 打開洛谷的模板題發現數據范圍是$10^7$,還是不能過,怎么辦。 先分析優化后的暴力的不足。

1.對於長度為奇數的回文和長度為偶數的回文,它們的對稱軸是不一樣的,要分類討論。 2.有些子串會被訪問多次。

比如 :

char: a b a b a b a ...
 i : 0 1 2 3 4 5 6 ...

我們枚舉到第$i$位: $i3$時第一個"aba"被遍歷了一次; $i4$時第一個"aba"又被遍歷了一次; $i5$時第一個"aba"雙被遍歷了一次; .......…… $ilen/2$時第一個"aba"又雙叒叕被遍歷了一次。

###處理字符串長度的奇偶性帶來的對稱軸不確定問題 如果字符串的長度都是奇數就好辦了。 處理原來的字符串,在收尾和所有空隙插入一個相同的無關的字符,插入后原字符串中是回文的子串還是回文串,不是回文的子串的依然不是。 但字符串的長度都變成了$2len+1$,都成了奇數. 為什么是$2len+1$,因為有$len-1$個空,兩邊又分別插入了$2$個,加起來等於$len+(len-1)+2=2*len+1$。 如:

長度為奇數的字符串 ababa ---> @#a#b#a#b#a# 長度為偶數的字符串 1221 ---> @#1#2#2#1#

'@'用來防止數組越界 ###找最長回文串 回文半徑:把一個回文串中最左或最右位置的字符到其對稱軸的距離稱為回文半徑 在Manacher算法中,我們用$p[i]$表示第$i$個字符的回文半徑

    char : # a # b # c # b # a #
    p[i] : 1 2 1 2 1 6 1 2 1 2 1
p[i] - 1 : 0 1 0 1 0 5 0 1 0 1 0
       i : 1 2 3 4 5 6 7 8 9 10 11

顯然,最大的$p[i]-1$就是答案

顯然這個結論非常不顯然,單從數值上看的話,插入完字符之后對於一個回文串的長度為原串長度*2+1,等於這個回文串回文半徑*2+1,顯然相等。

這樣我們的問題就轉換成了怎樣快速的求出$p$數組 在這里我們利用回文串的對稱性擴展回文串,p[i]不再直接賦值為1,而是根據之前求出的p[j],0<j<i。 我們用$mx$表示所有字符產生的最大回文子串的最大右邊界,$id$表示產生這個最大右邊界的對稱軸的位置。

為什么要維護這些東西,因為我們要利用回文串的對稱性來更新當前位置的值,維護了右邊界(mx)后就可以直接判斷當前位置是否可以直接利用對稱性來更新(因為之前找到的回文串最右端就是到$mx$,超出$mx$的話就不能利用對稱性來更新了);$id$是對稱軸,用來求關於$i$對稱的位置$j$。

未命名.jpg 中間的#懶得畫了就 如圖,假設我們已經求出了$p[1...7]$,當$i<mx$時,因為$id$被更新過了,而$i$是$id$之后的位置,第$i$個字符一定落在$id$的右邊。這時我們關心的還是$i$是在$mx$的左邊還是右邊。

以下內容為了方便我們定義: 串$i$表示以$i$為對稱軸的回文串(用紅色的箭頭表示); 串$j$表示以$j$為對稱軸的回文串(用藍色的箭頭表示); 串$id$表示以$id$為對稱軸的回文串(用綠色的箭頭表示);

####情況1:i < mx 如上圖,利用回文串的性質,對於$i$,我們可以找到一個關於$id$對稱的位置$j=id*2-i$,進行加速查找 但在這里又細分為了三種情況 (1) 未命名.jpg 顯然此時$p[i]=p[j]$。 對於這種情況,串$i$不可以再向兩邊擴張。 如果可以向兩邊擴張的話,$p[j]$也可以再向兩邊擴張,而$p[j]$已經確定了,所以串$i$不向兩邊擴張。 (2) 未命名.jpg 顯然此時$p[i]=p[j]$ 與(1)不同的是,串$i$是可以再向兩邊擴張的。 應該很顯然,一比划就知道。 (3) 未命名.jpg 此時$p[i]=mx-i$ 這時我們只能確定串$i$在$mx$以內的部分是回文的,並不能確定串$i$和串$j$相同。 同樣,這時我們的串$i$是不可以再向兩端擴張的。 未命名.jpg 如果串$i$可以擴張,如圖,則$d=c$,根據對稱性$c=b$,又因為$a=b$,所以$a=d$,可以看到,串$id$可以繼續擴張,因為$p[id]$已經固定了,所以串$i$不可以繼續擴張 ####情況2:i >= mx 5c7a2e7240b38.jpg 這時之前記錄的信息都用不上了,於是$p[i]=1$。 綜上所述

    if (i < mx) p[i] = min(p[id * 2 - i], mx - i);   //情況1
    else p[i] = 1;    //情況2
    while (str[i + p[i]] == str[i - p[i]]) p[i]++;    //暴力擴展
	if (p[i] + i > mx) mx = p[i] + i, id = i;    //更新id位置
}

##代碼 P3805 【模板】manacher算法

#include <bits/stdc++.h>
using namespace std;
const int N = 22000010;
char s[N];
char str[N];
int p[N];
int init() {
    int len = strlen(s);
    str[0] = '@', str[1] = '#';
    int j = 2;
    for (int i = 0; i < len; ++i) str[j++] = s[i], str[j++] = '#';
    str[j] = '\0';
    return j;
}
int manacher() {
    int ans = -1, len = init(), mx = 0, id = 0;
    for (int i = 1; i < len; ++i) {
        if (i < mx) p[i] = min(p[id * 2 - i], mx - i);
        else p[i] = 1;
        while (str[i + p[i]] == str[i - p[i]]) p[i]++;
        if (p[i] + i > mx) mx = p[i] + i, id = i;
        ans = max(ans, p[i] - 1);
    }
    return ans;
}

int main() {
    cin >> s;
    cout << manacher();
    return 0;
}   


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM