初學回文自動機
阿新 • • 發佈:2020-07-20
前言
迴文自動機,據說是解決迴文問題的利器。
最近剛好遇到一道迴文問題,加上正好閒著沒事幹,就來學了學。
感覺板子還是非常簡潔的,容易記憶。
節點資訊
與一般自動機類似,定義一個節點的資訊。
一般包括表示長度、\(fail\)指標、後繼節點,當然視具體題目還要維護一些特殊資訊。
但也要注意迴文自動機與一般自動機的區別,就是它的後繼\(x\)表示在當前串左右兩邊各加上一個字元\(x\)(要保證每個節點對應的串是迴文串)。
奇根與偶根
考慮迴文分為奇迴文和偶迴文,因此迴文自動機一個特殊的地方就是,它有兩個根:奇根以及偶根。
分別設定它們的初始資訊:
節點編號 | 表示長度 | \(fail\)指標 | |
---|---|---|---|
奇根 | \(1\) | \(-1\) | 奇根偶根皆可 |
偶根 | \(0\) | \(0\) | 奇根 |
\(fail\)指標
迴文串和一般的字串相比要相對複雜,因此它的\(fail\)指標也不能和其他自動機一樣直接呼叫,而需要為它寫一個專門的函式。
首先,一個節點的\(fail\)指標所指向的,是該回文串的最長迴文字尾所對應的節點。
但在具體使用時,由於迴文串的後繼節點要求在串兩側各加上一個字元,因此一個節點的\(fail\)指標並不一定在任何時候都能實現擴充套件。
因此,我們需要不斷跳\(fail\),直至找到一個合法的節點,滿足它可以向兩側擴充套件。
這一過程可以定義為一個函式\(Fail(x,id)\)
插入
插入一個新的位置\(id\),首先我們要通過\(t=Fail(lst,id)\)得到第一個可擴充套件位置。
如果\(t\)已經有對應兒子,就不需要操作了;否則,我們新建一個節點,節點長度就是\(t\)的長度加\(2\)(最後一次重複,迴文串每次在串兩側各加上一個字元),而\(fail\)指標就是繼續上跳,得到\(Fail(fail[t],id)\)。
這一過程是非常簡短,也非常簡單的。
程式碼(板子題)
#include<bits/stdc++.h> #define Tp template<typename Ty> #define Ts template<typename Ty,typename... Ar> #define Reg register #define RI Reg int #define Con const #define CI Con int& #define I inline #define W while #define N 500000 using namespace std; int n;char s[N+5]; class PalindromeAutomation { private: int Nt,lst;struct node {int L,V,F,S[30];}O[N+5]; I int Fail(RI x,CI id) {W(s[id-O[x].L-1]^s[id]) x=O[x].F;return x;}//根據實際情況找到合法的節點 public: I PalindromeAutomation() {O[O[0].F=Nt=1].L=-1;}//初始化節點資訊 I int Ins(CI id) { RI x=s[id]&31,t=Fail(lst,id),o;!O[t].S[x]&&(o=++Nt,//如果沒有對應兒子,新建節點 O[o].L=O[t].L+2,O[o].V=O[O[o].F=O[Fail(O[t].F,id)].S[x]].V+1,O[t].S[x]=o);//計算當前點資訊 return O[lst=O[t].S[x]].V;//返回 } }P; int main() { RI i,lst=0;for(scanf("%s",s+1),n=strlen(s+1),i=1;i<=n;++i) s[i]=(s[i]-97+lst)%26+97,printf("%d%c",lst=P.Ins(i)," \n"[i==n]);return 0; }