後綴數組 && 後綴自動機 總結
後綴數組(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)
後綴數組 && 後綴自動機 總結