例題 HDU-5421 翻譯
如果字串是靜態的,則可以使用 Manacher 演算法。但本題的字串可以動態在首位新增字元,無法使用 Manacher。
本題可以使用迴文樹(迴文自動機)演算法。該演算法的時間複雜度為 \(\mathcal{O}(N)\),但空間複雜度較差,因為其本質上是字典樹。
1 迴文樹的關鍵技術
迴文樹的關鍵技術為奇偶字典樹 \(+\) 字尾鏈跳躍。下面給出思維過程。
- 字尾鏈跳躍
比如在 abcb
後面插入 a
,則新插入的 a
與第一個 a
之間的所有字元構成了一個迴文串。
那如果是插入了 c
,其不能與開頭的 a
構成迴文串,該怎麼辦呢?
可以發現上面插入 a
時,本質上就是嘗試在原串的字尾迴文串 bcb
左右各擴充套件一個字元。如果新插入的字元與上一個迴文串的前一個字母相同,那這個迴文串就可以向左右各擴充套件一個字元了。但如果不同,那我們就應該找下一個短一點的迴文串進行嘗試,如上面的例子中嘗試擴充套件 bcb
失敗,接下來以最後一個 b
結尾的最長迴文串為 b
本身,我們再嘗試擴充套件它,擴充套件成功。b
擴充套件成為 bcb
。
我們稱這個不斷找最長字尾迴文串的過程為字尾鏈跳躍。
- 奇偶字典樹
我們可以用兩棵字典樹來儲存迴文串,一棵存長度為奇數的,一棵存長度為偶數的。我們可以用結點 \(0,1\) 分別表示偶數字典樹和奇數字典樹的根。在儲存迴文串時,我們只存一半。比如迴文串 abccba
,我們在偶數字典樹上加入 \(0 \to\) c
\(\to\) b
\(\to\) a
,這樣我們從下往上讀,讀到 \(0\) 後再讀下來就是原串。如果是 abcba
,那我們在奇數字典樹上加入 \(0 \to\) c
\(\to\) b
\(\to\) a
,這樣我們從下往上讀再讀回來,但最上面的邊只讀一次。
下圖是 zaacaac
的儲存方式:
- 建迴文樹