1. 程式人生 > >後綴數組 && 後綴自動機 總結

後綴數組 && 後綴自動機 總結

機智 其他 自己 pan 重要 CP 不同 由於 com

後綴數組(SA)

後綴數組的構造

放板子,其它的百度。 我當時學習看的博客:戳我的都是大佬

IL bool cmp(RG int i,RG int j,RG int k){return cy[i]==cy[j] && cy[i+k]==cy[j+k] ; }
IL void GetSA(){
    M = 30 ; 
    for(RG int i = 1; i <= n; i ++) pre[cx[i] = str[i] - 'a' + 1] ++ ;
    for(RG int i = 1; i <= M; i ++) pre[i] += pre[i - 1] ;
    for(RG int i = n; i >= 1; i --) SA[pre[cx[i]] --] = i ;
    for(RG int k = 1,p; k <= n; k <<= 1){
        p = 0 ;
        for(RG int i = n - k + 1; i <= n; i ++) cy[++p] = i ;
        for(RG int i = 1; i <= n; i ++) if(SA[i] > k) cy[++p] = SA[i] - k ;
        for(RG int i = 1; i <= M; i ++) pre[i] = 0 ;
        for(RG int i = 1; i <= n; i ++) pre[cx[cy[i]]] ++ ;
        for(RG int i = 1; i <= M; i ++) pre[i] += pre[i - 1] ;
        for(RG int i = n; i >= 1; i --) SA[pre[cx[cy[i]]] --] = cy[i] ;
        for(RG int i = 1; i <= n; i ++) swap(cx[i] , cy[i]) ;
        cx[SA[1]] = p = 1 ;
        for(RG int i = 2; i <= n; i ++) cx[SA[i]] = cmp(SA[i - 1] , SA[i] , k) ? p : ++ p ; 
        if(p >= n) break; M = p ; 
    }
    for(RG int i = 1; i <= n; i ++) Rank[SA[i]] = i ;
    for(RG int i = 1 , j = 0 ; i <= n; i ++){
        if(j) -- j ;
        while(str[i + j] == str[SA[Rank[i] - 1] + j]) ++ j ;
        Height[Rank[i]] = j ; 
    }return ; 
}

一些總結出來的套路

二分 + \(Height\)分組

根據\(Height\)的定義,二分答案後可以將所有後綴按照 $lcp \ge $ 二分值 進行分組。
然後查看是否存在某組滿足題目要求即可。

問:詢問串中出現至少3次的子串最長有多長。
解:二分答案\(mid\),然後進行\(Height\)分組,查看是否存在一組的\(size \ge 3\) 即可。

多串匹配問題

後綴數組顯然處理不了多串。
所以把多個串依次拼在一起,相鄰兩串見放一個特殊字符即可。

:求\(K\)個串的最長公共子串(\(K\leq 10\))
:把串拼起來,二分一個答案\(Len\),按照\(Height\)

分組,查看是否某組中存在所有串即可。

\(ST\)表實現\(lcp\)的快速查詢

比較基礎的內容。 對\(Height\)數組建立\(ST\)表。
那麽查詢\(suffix_i\)\(suffix_j\)\(lcp\)就可以直接查詢\(min\{ (rank_i, rank_j]\}\)

本質不同的子串

考慮到排名相鄰兩個後綴的\(Height\)即為它們的\(lcp\)
所以對應減少了這麽多個本質不同串。
綜上所述,一個串的本質不同子串個數為:子串總個數 - \(\sum Height\)

前綴轉後綴

從現在開始說一些不那麽弱智的東西......
由於後綴數組只能接收後綴信息。
所以例如在後面插入一個字符,就需要轉化為在前面插入一個字符。

同時:任何一個子串都是一個後綴的前綴
這個非常重要。
例如,我們要快速得知一個子串的出現次數,
那麽,我們首先找到對應後綴在\(Height\)裏的位置(即這個後綴的\(Rank\))。
然後,設這個串的長度為\(len\),那麽答案即與這個對應後綴 \(lcp \ge len\) 的後綴個數。
這個顯然在\(Height\)數組中是一段連續的區間,我們可以通過二分得到對應的左右端點。
這樣單次查詢的復雜度就變成了\(logn\)了。

並查集合並

還是註意到\(Height\)的含義,表示\(lcp(str_{SA_i},str_{SA_{i-1}})\)
那麽在求\(\sum_{i=1} \sum_{j=1} lcp(str_i,str_j)\)這類東西的時候是不是有點爽?
我們可以把\(id\)按照\(Height\)從大到小排序。
(註意\(Height_1=0\),所以\(id_1\)不能加入操作中!)
然後每個並查集維護一個後綴集合,再按照\(Height\)從大到小合並集合。
考慮我們合並兩個集合\(A\)\(B\)的時候。
由於我們是按照\(Height\)從大到小排序的,設當前\(Height\)\(H\),
所以\(lcp(A_i,A_j) \ge H\)\(lcp(B_i,B_j) \ge H\)
又因為合並的連接點\(lcp(A_0,B_0) = H\)。所以\(lcp(A_i,B_j) = H\)
所以.....不要問我為什麽這麽機智(QwQ呵呵呵)。

總結

感覺相比於其他算法(如後面的SAM)來說,後綴數組比較好理解。
題目一般也不難,都是現成的套路直接套。
關鍵還是要把套路的模型給建出來,然後不要寫掛!
還是有一些神仙題(比如NOI2016優秀的拆分),這種題目就只能靠自己了。

後綴自動機(SAM)

後綴數組 && 後綴自動機 總結