1. 程式人生 > 其它 >字尾陣列題目選講

字尾陣列題目選講

字尾陣列題目選講

字尾陣列題目選講

複習題:luogu題單

CNOI的題

\(1.\)NOI2015 品酒大會

題意:\(\forall i∈[0,n)\)求有多少對字尾滿足\(lcp \ge i\)​,以及滿足條件的兩個字尾的權值乘積的最大值。

我們統計出對於\(1...n\)中的每個\(i\)統計一下有多少個\(lcp=i\)再做個字尾和。

因為\(lcp(i,j)=min_{i<k \le j}{heiht_k}\),我們考慮每個\(heiht_k\)能產生的貢獻是一段區間。

這個是個單調棧經典問題,求出包含\(heigt_k\)\(heigt_k\)是最大值的最長區間,然後左右長度相乘即可。

而最值可以用\(st\)

表維護,維護最大和次大,最小和次小,因為最大乘積可能由兩個負數相乘得出。

\(2.\)NOI2016 優秀的拆分

計算出\(st_i\)​​​表示以\(i\)​​​​開頭的\(AA\)​​​串個數,\(ed_i\)​​​表示以\(i\)​​​結尾的\(AA\)​​​串個數,我們用\(hash\)​​可以\(O(1)\)​​判斷一個子串是否為\(AA\)​​串,答案就是\(\sum_{i=1}^{n-1}st_{i+1}\times ed_i\)​​,複雜度\(O(n^2)\)​​,這樣就可以拿到\(95pts\)​了​​​​​​,最後5pts打表就行了

考慮如何更快的求出\(st\)​和\(ed\)

​,我們列舉 \(AA\)​ 型字串中\(A\)​的長度\(L\)。每隔\(L\)格放一個關鍵點,即在\(L,2L,3L...\)位置放關鍵點。顯然,一個長度為\(2L\)\(AA\) 型字串應當經過恰好兩個關鍵點。

\(lcp(i,j)\)​​為以\(i,j\)​字尾開頭的最長公共字首,\(lcs(i,j)\)​為以\(i,j\)​字尾結束的最長公共字尾,這個可以建正反串字尾陣列求出。考慮兩個相鄰的關鍵點\(i,j\)​,其中\(i+l=j\)​ ,那麼顯然當\(lcp(i,j)+lcs(i,j) \ge L\)​時產生了貢獻,貢獻為\(lcp(i,j)+lcs(i,j)-L+1\)​​

如圖紅色是\(i,j\)​,藍色和綠色分別是\(lcp\)​和\(lcs\)​,其中粉色是\(AA\)​串,而這個\(AA\)​串可以一直向後滑動到棕色部分,滑動中經過的串的個數就是\(lcp(i,j)+lcs(i,j)-L+1\)​​,所以我們把紅色加粗部分的所有\(st_i\)​加\(1\)​,把綠色加粗部分的所有\(ed_i\)​加\(1\)​,這個用差分實現即可。

複雜度:\(O(nlog_2n+n+\frac n 2+\frac n 3+...+1) \approx O(n \log_2 n)\)

\(3\)​​.NOI2018 你的名字

題意: 給你一個字串\(S\), 多次詢問給定一個\(T\), 求\(T\)中不在\(S[l...r]\)​中出現的本質不同的子串個數。

老套路,把詢問離線,把所有串用一個特殊字元連在一起做字尾陣列。

我們按順序列舉\(T\)​​的每個字尾\(i\)​​,我們求出最大的長度\(L\)​​使得\(\large T[i,i+L-1]\)​​是\(S[l...r]\)​​的子串,那麼長度小於\(L\)​​的顯然都不滿足條件,可以直接計算。這個\(L\)​​是可以二分的,但是我們發現所有的\(i+L-1\)​​這個位置隨著\(i\)​​的增大是單調上升的,所以雙指標即可。

所以現在只要判斷一個\(L\)​是否可行。事實上就是查詢\(S[l...r-L+1]\)中是否存在一個\(k\)使得\(lcp(k,i) \ge L\)​,我們先求出滿足這個條件的\(k\)在後綴陣列上的區間\([Le,Ri]\),然後判斷\([Le,Ri]\)中是否存在一個字尾\(k \in [l,r-L+1]\)​,使用主席樹查詢即可。

用本質不同子串個數減去上述得到的答案即可。

時間複雜度\(O(nlog_2n)\),注意常數。

\(4.\)​[HEOI2016/TJOI2016 字串

考慮二分答案,二分一個\(L\)只需要判斷是否存在一個\(k \in [b-L+1,b]\)使得\(lcp(k,c)=L\),根據上題的做法,先二分出在後綴陣列的對應的區間\([Le,Ri]\)​,然後主席樹查詢即可。

時間複雜度\(O(nlog_2^2n)\)

CF的題

\(5.\)CF504E

先樹剖再按\(dfn\)序建立正反字尾陣列,跳重鏈的時候記錄下重鏈的左右端點,然後拼起來。

每次我們用\(st\)表求出\(lcp(i,j)\)​然後根據長度判斷是否還能向擴充套件。

因為兩個點之間重鏈個數最多隻有\(2log_2n\)個,所以我們最多比較\(4log_2n\)次。

這個做法常數很小,最慢的點只要2s,目前我的賬號小涵\(luogu\)​​​的最優解

\(6\).CF666E

老套路,把所有串用一個特殊字元連在一起做字尾陣列。

容易發現答案就是對於所有滿足\(lcp(pl,k) \ge pr-pl+1\)​中\(col_k \in [l,r]\)​的眾數。

我們考慮將詢問按 \(pr-pl+1\)​ 從大到小排序,再把\(heiht_i\)​ 也從大到小排序,用兩個指標,一個指向當前詢問,一個指向當前的\(heiht_i\)​,我們將所有\(height_i \ge pr-pl+1\)​的進行合併操作。也就是把\(i\)​和\(i-1\)​​​​所在的集合合併,用資料結構維護眾數即可,很顯然用並查集+線段樹合併可以很好的維護這個資訊。這題的做法和noip2013 貨車運輸的思想是一樣的,所以事實上也可以用\(kruskal\)​​重構樹維護眾數。

\(7.\)CF1037H

我們列舉答案與\(T\)\(lcp\)長度\(L\),找出在後綴陣列上對應的區間\([l,r]\),因為新增一個字元只會使區間縮小,所以我們用兩次二分可以確定新一輪\([l,r]\)

我們再枚第\(L+1\)​個應該填什麼字元,這個字元應滿足字典序是大於原字串的對應位置的字元,按照上述同樣的方法求出\([l,r]\)​,然後判斷\([l,r]\)​中是否\(\exist~sa_k\in[Le,Ri-L]\)​,這個用主席樹維護即可。