后綴自動機練習專題
一些比較有用的東東:
-
(1) \(\text{sam}\) 上一條從初始狀態出發的路徑對應一個子串
-
(2) \(\text{parent}\) 樹上一個節點能表示的最長的串對應一個前綴/后綴
-
(3) \(len(u)\) 表示節點 \(u\) 能表示的最長串的長度
-
(4) \(fa(u)\) 表示節點 \(u\) 的后綴鏈接指向的節點,也就是其在 \(\text{parent}\) 樹上的父親
-
(5) 表示兩個后綴的公共前綴的節點是兩個后綴在 \(\text{parent}\) 樹上的 \(lca\)
-
(6) \(R(u)\) 表示節點 \(u\) 的 \(\text{right}\) 集合,\(sz(u)\) 表示 \(R(u)\) 的大小,\(R(u) \subset R(fa(u)), sz(u) < sz(fa(u))\)
-
(7) 廣義 \(\text{sam}\) 可以理解為一個 \(\text{sam}\) 維護多個串,每一次插入完一個串后將 \(tail\) 指針設為初始狀態
## SPOJ 1811 Longest Common Substring
給出兩個串 \(s, t\),求這 \(s, t\) 的最長公共子串,\(|s|, |t| \leq 2.5 \times 10^5\)
對於 \(s\) 建 \(\text{sam}\) ,根據 (1) 可以將 \(t\) 放進 \(\text{sam}\) 匹配,當失配時相當於要要從當前匹配到的串的一個后綴繼續開始匹配,等價於在 \(\text{sam}\) 上跳 \(fa(u)\) 。可以證明這樣一定能匹配到最長公共子串。
## SPOJ 1812 Longest Common Substring II
給出 \(n\) 個串,求這個 \(n\) 個串的最長公共子串,\(1 \leq n \leq 10, |s| \leq 10^5\)
和上題類似的, 還是取出一個串建 \(\text{sam}\),可以求出每一個節點被多少個串匹配到了,那么答案就是 \(\max(len(u))\),\(u\) 是被 \(n - 1\) 個串匹配到的節點。考慮每一次匹配到某個節點的時候,根據 (6),其祖先代表的串也會被匹配到,所以每一個串匹配完以后還要更新其祖先的匹配次數,暴力向上跳即可。
另外一種做法,考慮串數只有 \(10\) ,可以建一個廣義 \(\text{sam}\) 將所有串放進去,用二進制維護 \(R(u)\) 是否包含來自某個串的后綴,當一個節點包含來自所有串的后綴時,其就可以對答案產生貢獻。當 \(n\) 比較大的情況可以用 \(\text{bitset}\) 或者線段樹合並來維護。
## SPOJ 7258 Lexicographical Substring Search
給出一個串 \(s\),以及\(q\) 次詢問,每次詢問 \(s\) 中第 \(k\) 小的子串,重復的子串僅算一次, \(|s| \leq 9 \times 10^4, q \leq 500\)
可以不考慮 \(\text{parent}\) 樹,只考慮 (1) ,計算出每一個節點向下走能走到的路徑數量。由於要求的是字典序第 \(k\) 小,每一次二分出從這個節點出發應該走的轉移邊的字符是什么,向下走即可。這樣做復雜度是 \(O(|s|q)\),由於 \(q\) 不大,足以通過此題。
實際上考慮 \(\text{parent}\) 可以進一步優化算法的復雜度,考慮原先的 \(\text{parent}\) 樹一個節點代表的多個串都是最長的串的一個后綴,是一棵類似於前綴樹的結構,這樣不能適用於一些字典序上優美的性質。不妨將串反序插入到\(\text{sam}\) 中,這樣每一個點能代表的多個串都是最長的串的前綴,這些串從長到短在字典序上一定是有序的。擴展到整棵樹上,根據 \(mn(u) = len(fa(u)) + 1\) ,每個點代表的字符串都比其祖先代表的字符串的字典序大。於是可以計算出每一棵子樹代表了多少串,在 \(\text{dfn}\) 序上二分答案即可,復雜度是 \(O(qlogn)\)。
## 「TJOI2015」弦論
給出一個串 \(s\) ,根據題目要求來求出相同子串算一個或多個的第 \(k\) 小子串 \(|s| \leq 5 \times 10^5\)
第一問和上一題完全等價,考慮第二問的情況,一個子串在 \(s\) 中的出現次數就是其對應 \(\text{parent}\) 樹節點的 \(\text{right}\) 集合大小,也就是 \(sz(u)\) ,只需要對於每一條路徑把權值為 \(sz(u)\) 進行統計即可。
## Codechef January Challenge 2018 - Killjee and k-th letter
給出一個的串 \(s\),將 \(s\) 所有子串按照字典序排列好相接起來形成一個新串,\(q\) 次詢問,每一次詢問問新串中的第 \(k\) 個字符是什么,強制在線。\(|s|, q \leq 2 \times 10^5\)
考慮上上題給基於 \(\text{parent}\) 的做法,反序插入后每個節點代表的字符串在 \(\text{dfn}\) 序上是有序的,所以只要求出每一個節點代表的串的數量,然后對 \(\text{dfn}\) 序進行二分求出答案在哪個節點代表的子串上。考慮一個子串其所代表的串長度是從 \([len(fa(u))+1,len(u)]\) 連續的,可以直接求出 \(k\) 對應的子串是從節點 \(u\) 對應的后綴的多少位,查找該位置在原串上的字符即可
Codeforces 873 F. Forbidden Indices
有一個串 \(s\),\(s\) 的有些位置是非法的,求所有不以非法位置為結尾的子串 \(a\) ,\(\sum|a|\times tot(a)\) 的值,其中 \(tot(a)\) 表示 \(a\) 在 \(s\) 中的出現次數。\(|s| \leq 2 \times 10^5\)
\(\text{parent}\) 樹上一個節點代表的串的總數就是 \((len(u)-len(fa(u))) \times sz(u)\) ,維護出每一個串不含非法位置的 \(\text{right}\) 集合大小后直接求和
## 「AHOI2013」差異
給出一個串 \(s\) ,對於其所有后綴 \(t_i, t_j\) ,求 \(\sum len(t_i)+len(t_j) - 2len(lcp(t_i, t_j))\) 的值, \(|s| \leq 5 \times 10^5\)
考慮將串反序插入到 \(\text{parent}\) 樹后,兩個后綴節點的 \(\text{lcp}\) 就是他們的 \(\text{lca}\) ,那么可以樹形 \(\text{dp}\) 出以每一個節點作為 \(\text{lca}\) 時得到的 \(\sum len(t_i)+len(t_j) - 2len(u)\) ,最后對所有節點求和就是答案
## 「NOI2015」品酒大會
給出一個串 \(s\) 和一個序列 \(a\) ,對於所有 \(i\) ,求出 \(lcp(x, y) \geq i\) 的方案數以及滿足條件的最大的 \(a_x \times a_y\) \(|s| \leq 3 \times 10^5|a_i| \leq 10^9\)
本質上和上一題做法一樣,考慮第一問只需要求出對於每一個節點 \(u\) ,在其子樹里選出兩個節點 \(\text{lca}\) 為 \(u\) 的方案數即可,第二問稍有復雜,因為權值可正可負,需要分類討論 \(\text{dp}\) 維護最大值和最小值,便於合並相乘時統計答案
## 「BZOJ3473」字符串 (雙倍經驗=3277)
給出 \(n\) 個字符串,求有多少個子串是其中至少 \(k\) 個字符串的子串,\(\sum|s| \leq 10^5, 1\leq k \leq n\)
建立一個廣義 \(\text{sam}\) ,每次插入一個串后維護一下 \(\text{parent}\) 樹上哪些節點的 \(\text{right}\) 集合已經擁有了來自這個串的后綴,這樣每一個新增的插入節點都需要向祖先更新這個信息,根據均值不等式分析,插入所有串的總復雜度是 \(O(|s|\sqrt|s|)\)。
考慮可以直接用線段樹合並維護每一個節點的 \(\text{right}\) 集合有哪些串的后綴,全部建完以后向上合並更新祖先的答案,總復雜度 \(O(|s|log|s|)\)。
## 「ZJOI2015」諸神眷顧的幻想鄉
有一棵 \(n\) 個節點的樹,每個節點上有一個字符,現在把每一條簡單路徑看成一個串,求本質不同的子串數量,\(n \leq 10^5\) 樹的葉子節點數量 \(\leq 10\)
如果一條簡單路徑被另外一條簡單路徑包含,那么其代表的串完全可以看成另外一個串的子串,所以我們只需要考慮不被任何串包含的簡單路徑,其數量顯然是葉子節點個數的平方。於是暴力將這些串插入到廣義 \(\text{sam}\) 中對不同子串數量計數即可。
## 「SDOI2016」生成魔咒
一開始有一個空串,一共有 \(n\) 次操作,每一次操作在這個串末尾加一個字符,求每一次操作結束時串中不同子串的數量 \(n \leq 10^5\)
\(\text{sam}\) 是在線構造的,每一次插入節點 \(p\) 后維護一下 \(len\) 發生改變的節點對答案的貢獻即可
## 「BZOJ2555」 Substring
在當前字符串的后面插入一個字符串
詢問字符串 \(s\) 在當前字符串中出現了幾次?(作為連續子串) 你必須在線支持這些操作。
喪心病狂的一道題,由於在線插入串到 \(s\) 后還要維護每一個點的 \(sz(u)\) ,所以要動態支持給 \(\text{parent}\) 樹加邊,維護子樹大小可以轉為鏈加,用 \(lct\) 來維護即可
## Codeforces 666 E. Forensic Examination
給你一個母串和很多詢問串,每次詢問求母串的一段區間在一段連續詢問串中出現最多的詢問串的出現次數和編號 \(1 \leq |s|, q \leq 5 \times 10^5\)
對所有串建一個廣義 \(\text{sam}\) ,每次詢問倍增找到左端點在 \(\text{parent}\) 樹上對應的節點,用線段樹合並維護出每個節點的 \(\text{right}\) 集合中每個詢問串的出現次數,區間查詢 \(\max\) 即可
## 「TJOI / HEOI2016」字符串
## 「NOI2018」你的名字 (非正解)
## 「BJWC2018」 Border的四種求法
萌萌噠的傳送門= =