字符串算法總結

前言

標題是騙你進來的,其實裏面全是題目。
最近一直在搞字符串......
把一些有表明性或者有必定難度的題放在這裏作一個總結。算法

[CF666E] Forensic

給你一個串\(S\)以及一個字符串數組\(T[1..m]\)\(q\)次詢問,
每次問\(S\)的子串\(S[p_l..p_r]\)\(T[l..r]\)中的哪一個串裏的出現次數最多,並輸出出現次數。
若有多解輸出最靠前的那一個。數據範圍:\(|S|,\sum |T|,q\leq 10^5\)數組

\(T[1...m]\)創建廣義\(SAM\),用線段樹合併維護出現次數。
每次查詢先倍增到相應結點,而後直接線段樹區間查詢。spa

[NOI2011] 阿狸的打字機

給定一個打字機,有加字符、刪字符、打印三種操做。
給定操做序列\(S\),而後有\(Q\)次詢問,每次回答第\(x\)次打印的串在第\(y\)次打印的串中出現幾回。
數據範圍:\(|S|,Q\leq 10^5\)指針

用棧模擬建出全部打印串的\(Trie\)樹,而後構建\(AC\)自動機與\(fail\)樹。
每次詢問至關於問\(x\)\(Trie\)的根這條路徑上有多少個點可以跳\(fail\)跳到\(y\)
\(Trie\)進行\(dfs\),用樹狀數組維護\(fail\)樹的子樹和。調試

[BZOJ3670] 動物園

給定一個串\(S\),求每個前綴的不相交\(border\)數。 數據範圍:\(|S|\leq 10^7\)code

解法一:建出\(fail\)樹,而後\(dfs\)一遍\(fail\)樹,用單調隊列維護合法前綴大小。
解法二:先預處理出\(next\)數組,而後相似\(next\)數組的求法求答案,當不合法時跳\(next\)數組。排序

[SCOI2013] 密碼

給定一個串的每一個迴文中心的擴展大小,構造知足條件的最小字典序串。數據範圍:\(n\leq 10^5\)隊列

對這個串跑一遍\(manacher\),直接模擬便可,用並查集維護兩個位置的字符是否相同。
最後使用最小表示法求出答案串。字符串

[NOI2015] 品酒大會

給定一個串\(S\),求\(\sum_{i} \sum_{i\neq j} lcp(suf_i,suf_j)\)。數據範圍\(|S|\leq 10^5\)string

使用後綴數組,按照\(Height\)排序後合併後綴,用帶權並查集維護答案。
或者建出\(SAM\)\(fail\)樹後,直接\(dfs\)一遍\(fail\)樹,每次直接合並子樹答案。

[POI2005] SZA-Template

求一個最短的模板串\(T\),使得用\(T\)對長度爲\(n\)的串反覆染色後能夠獲得\(S\)
注意:同一個位置能夠屢次染色,但只能染一種顏色。
數據範圍:\(n\leq 5*10^5\)

顯然\(T\)\(S\)的一個\(border\)
那麼限制條件就是\(T\)在串\(S\)中的出現位置的最大間距不能超過\(|T|\),其中\(T\)爲一個\(border\)
注意到\(border\)匹配的特殊性,當一個前綴\(pre\)\(border\)\(T\)時,\(T\)就在該點匹配上了一次。
因此跑\(kmp\)後建出\(fail\)樹。
對於一個\(T\),它合法的充要條件爲子樹內的點之間的間距不超過\(|T|\),直接用\(set\)維護最大間距。

[BZOJ4310] 跳蚤

給一個串\(S\),把它劃分爲最多\(K\)段,
而後挑選出全部段中的最大字典序串,再把這些串取最大字典序串,稱獲得的串爲\(T\)
最小化\(T\)的字典序並輸出\(T\)。 數據範圍:\(|S|\leq 3*10^5\)\(K\leq 20\)

最大最小化問題,首先二分\(T\)\(S\)的全部子串中的排名。
而後貪心驗證。因爲咱們的後綴數組是之後綴排名的,因此從後往前掃。
若當前的後綴排名小於等於\(T\),則直接跳過。
不然須要考慮割一刀,先求\(lcp\),而後查看\(lcp\)長度範圍內是否割了一刀,若是沒有就割一刀。
最後比較割的次數與\(K\)的關係便可。
須要實現的功能有快速求\(lcp\),給串求排名,給排名求串,這些均可以用\(SA\)解決。

[HihoCoder1413] Rikka-with-String

給一個串\(S\),求把每個位置替換爲特殊字符#後本質不一樣的子串個數。
數據範圍:\(|S|\leq 10^5\)

先構建\(SAM\),把原串本質不一樣的子串個數求出來,而後考慮變化量。
首先增長量很顯然是\(i(n-i+1)\) ,考慮求減小量。
對於\(SAM\)上的每個點,咱們維護其最大\(endpos\)與最小\(endpos\)
而後做圖能夠發現,這個結點對兩個區間的貢獻分別爲 等差數列 和 一個常數。
差分便可。

[BZOJ2384] Match

給定兩個排列\(S,T\),要求\(T\)\(S\)中匹配了多少次。
這裏的匹配定義爲:相對大小相同即算匹配上。
數據範圍:\(|S|,|T|\leq 10^6\) 。空間限制:\(64MB\)

魔改一下\(kmp\)算法。
對於\(T\),咱們能夠求出一個\(next\)數組,表示失配後到達位置,而後在\(S\)上相似的跑\(kmp\)便可。
如今的問題變爲:求一個區間內大於某個數的個數,直接想法是主席樹,但會\(MLE\)
深度發掘一下\(kmp\)的原理,發現它其實相似一個雙指針。
因此使用樹狀數組,在跳\(next\)的時候暴力把刪除元素在樹狀數組中刪掉。

[CF932G] Palindrome

給定一個偶數長度的串\(S\),試着把它劃分爲偶數段。
設劃分爲了\(k\)段,那麼須要知足\(s_i=s_{k-i+1}\),求方案數。數據範圍:\(|S|\leq 10^6\)

首先構建串\(S'= s_1s_ns_2s_{n-1}...\),問題轉化爲把\(S'\)劃分爲若干偶數迴文串的方案數。
對於一個迴文串,
若它的若干迴文後綴都不超過其長度的一半,則這些迴文後綴必定構成等差數列。
這樣的話,迴文樹上的某個結點的全部祖先最多隻會構成\(log\)個等差數列。
咱們考慮讓同一等差數列中的點一塊兒轉移,維護\(up\)表示最淺的非等差位置。
對於同一等差數列中的點,因爲其長度不超過原串長度的一半,
故咱們對稱後恰好只有\(up\)處的貢獻沒有加上,因此暴力跳等差數列的同時把該貢獻加上便可。

[NOI2018] 你的名字

給定一個串\(S\),有\(Q\)次詢問,每次給定一個串\(T\),求不在\(S[l,r]\)中出現的\(T\)的子串個數。
數據範圍:\(|S|,Q,\sum |T| \leq 5 * 10^5\),其中\(l,r\)也是每次詢問給定的變量。

\(S\)創建後綴自動機,用線段樹合併獲得其\(endpos\)集合。
對於每次詢問,先對\(T\)創建後綴自動機,求的\(T\)的子串個數,而後再把在\(S\)中出現的子串減掉。
對於\(T\),咱們求出其每一個前綴的最長匹配後綴\(lim\)
那麼對於\(T\)\(SAM\)上的每一個點,利用\(lim\)把非法貢獻減掉便可。
考慮求\(lim\),直接把\(T\)放在\(S\)\(SAM\)上作匹配,經過線段樹查詢\(endpos\)判斷是否出現。
當失配的時候,不能直接跳\(fail\),而應該要使長度減一,而後再次檢查。
能夠發現這個過程當中,\(T\)和匹配長度\(len\)的關係相似一個雙指針,因此複雜度是沒有問題的。

[NOI2016] 優秀的拆分

給定一個串\(S\),求全部子串劃分爲\(AABB\)形式的方案數之和。數據範圍:\(|S|\leq 10^5\)

驚現\(NOI\)史上最良心出題人,白送\(95\)分簡直搞笑。考慮最後\(5\)分應該怎麼拿。
顯然\(AABB\)是一個障眼法,咱們其實只用求\(AA\)的劃分方案就好了。
開始構造,枚舉一下\(A\)的長度\(len\)
而後對於\(S\),每一個長度\(len\)咱們就設置一個頂標點\(p_i\)
那麼對於任意\(|A|=len\)\(AA\)串,它必定剛好通過兩個頂標點。
因此對於相鄰兩個頂標點,求一下它們的最長公共前、後綴,而後算一下貢獻便可。

[CTSC2010] 珠寶商

給定一個串\(S\)和一棵\(n\)個結點樹,其中樹上的每個結點有一個字符。
求樹上每一條有序路徑構成的字符串在\(S\)中的出現次數之和。數據範圍:\(n,|S|\leq 50000\)

樹上路徑問題考慮點分治,創建點分樹。因爲\(n\)範圍比較小,因此能夠數據均攤分治。
對於點分子樹大小\(\leq \sqrt{n}\)的點,咱們直接暴力作,複雜度\(O(\sqrt{n}^3) = O(n\sqrt{n})\)
對於點分子樹大小\(> \sqrt{n}\),這類點顯然只有\(\sqrt{n}\)個,咱們嘗試用其它理論解決。
考慮獲得了兩條路徑,而後把它們拼接在一塊兒。
那麼對應到字符串上,就是一個前綴和一個後綴進行拼接。
因此咱們只須要知道每個前綴的方案數和每個後綴的方案數,而後再乘一下就好了。
咱們以求前綴方案數爲例,後綴方案數反過來作便可。
觀察一下匹配過程,咱們肯定了一個\(endpos\)字符,而後須要向前作匹配。
這顯然是\(SAM\)\(DAG\)\(fail\)樹難以作到的。
因此咱們須要將\(SAM\)生成的\(fail\)樹進一步處理,獲得其前綴樹,而後匹配就是跳前綴樹的子樹。
最後的問題在於如何快速給匹配的點的全部\(endpos\)加上貢獻。
回顧一下\(endpos\)集合的獲得方法,不難發現只須要把這個過程給逆過來就好了。
咱們先在當前點打上標記,所有匹配完後,順着\(fail\)樹把標記向下推送。
那麼對於一個前綴,它在\(fail\)樹上對應的結點必定是一個葉子結點,咱們直接在該葉子查答案。

[八省聯考2018] 制胡竄

給定一個串\(S\)
\(Q\)次詢問,每次求把串劃分爲非空三段,且三段中至少包含一個\(S[pl,pr]\)的方案數。
數據範圍:\(|S|,|Q|\leq 100000\),其中\(pl,pr\)爲每次給定的變量。

此題代碼細節賊多,若是要寫請務必作好代碼調試至少一個晚上的準備。
正難則反,考慮求不包含\(S[pl,pr]\)的方案數。
爲了書寫方便咱們令\(T = S[pl,pr]\),同時定義\(T\)\(S\)中的出現次數爲\(n\)
定義\(T\)\(S\)中的出現串爲\(p_1,p_2...p_n\),其中\(l_{p_i},r_{p_i}\)表示它們的左右端點。
\(mn = p_1,mx = p_n\),令\(len = pr-pl+1\)
咱們如今的目標就是用兩刀切掉\(S\)中出現的全部\(T\)
大力討論:

( 1 ) 若存在三個不相交的\(T\),此時顯然無解。
( 2 ) 不然,若\(r_{mn} > l_{mx}\),即存在一刀流切法。

  • 若第一刀不是一刀流,枚舉第一刀切掉了哪些\(T\),則有:
    \(Ans = \sum_{i=1}^{n-1} (r_{p_{i+1}} - r_{p_{i}})(r_{mn} - l_{p_{i+1}})\)
    化簡有\(Ans = (r_{mn}-len+1) \sum_{i=1}^{n-1} (r_{p_{i+1}} - r_{p_i}) - \sum_{i=1}^{n-1} r_{p_{i+1}} (r_{p_{i+1}} - r_{p_i})\)
  • 若第一刀是一刀流,那麼第一刀的落刀範圍爲\([l_{mx},r_{mn}]\),考慮第二刀的位置。
    若第二刀也落在\([l_{mx},r_{mn}]\),這種狀況的方案數顯然爲\(\binom{r_{mn} - l_{mx}}{2}\)
    不然能夠發現第二刀落在\([l_{mn},l_{mx}-1)\)的方案已經算過了,
    故只用算落在其它位置的方案數,這個仍是比較好算的。
    若落在右側,則第一刀落在\([l_{mx},r_{mn})\),第二刀落在\([r_{mn} , n]\)
    若落在左側,則第一刀落在\((l_mx,r_{mn}]\),第二刀落在\([1,l_{mn}]\)
    因此這種狀況下的方案數爲\(Ans = \binom{r_{mn} - l_{mx}}{2} + (r_{mn} - l_{mx}) (n-len)\)

( 3 ) 不然,即\(r_{mn} \leq l_{mx}\),即不存在一刀流切法。
順着上一種思路,依舊考慮枚舉左邊那刀切掉了哪些\(T\)
那麼有:\(Ans = \sum_{i} (r_{p_{i+1}} - r_{p_i}) (r_{mn} - l_{p_{i+1}})\)
可是因爲不存在一刀流,因此必定會有一個\(i\)\(r_{mn}\)限制,致使切割範圍不是完整區間。
因此咱們把左端點最靠近\(r_{mn}\)的那個點先丟掉,設其爲\(p_t\)
而後就能夠獲得一個關於\(i\)的限制條件:\(r_{i+1} > l_{mx}\)\(l_i < r_{mn}\)
這裏須要注意一個天坑(大家就使勁感謝我吧):
咱們找到的是最小的符合條件的\(r_{i+1}\),而計算區間端點應該是\(r_i\),因此這裏須要找一次前趨。
而後咱們再把以前丟掉的那個點\(p_{t}\)給撿回來,它的答案應該是\((r_{mn}-l_{p_t})(r_{p_{t+1}} - l_{mx})\)
QaQ
累死我了,要是上面哪裏寫錯了麻煩各位吱一聲。
到此爲止咱們已經討論了全部狀況。
惟一的問題在於如何實現,
能夠注意到上面全部式子中與\(endpos\)有關的只有\(\sum r_i(r_{i+1}-r_i),\sum r_i\)
因此徹底能夠直接使用線段樹維護區間最大、最小值,進而使得這兩個信息能夠合併。
同時咱們發現,維護區間最大、最小值更是一併解決了找前趨、後繼的問題。
因此咱們對\(S\)\(SAM\),把詢問離線,而後按拓撲序逆序進行線段樹合併,做出相應回答便可。 至此問題終於解決。

相關文章
相關標籤/搜索