【前言】
字尾陣列真神奇。
【字尾】
對於乙個字串,定義suffix(i)為以i起點至結尾的子串。例如ababc,suffix(3)=bc
【字尾樹】
字尾樹容易理解,就是把乙個字串的所有字尾串插入到乙個字典樹上(字典樹不再累贅)。如圖為"ababc"的字尾樹:
建造這棵樹的代價是o(n*n)的空間複雜度,沒什麼實用性,只是為了更好的理解字尾陣列。注意,對於每個分支,我已按從左到右的順序排列,即最左端的suffix(0)就是所有字尾串中字典序最小的。
而我們想要的所謂的字尾陣列就是 sa= ,可以理解為一張成績單,成績是字典序從小到大。
【字尾陣列】
字尾樹中已提到sa陣列是一張成績單,我們有了這個成績單,就厲害了。比如我們想知道某個字串是否出現在原字串中,就可以在成績單上二分查詢,由於比較函式strcmp線性複雜度,因此總時間複雜度為o(n*longn)。
【字尾陣列的求解】
最直白的方法就是所有字尾串直接sort,但是代價是o(n*n)。下面介紹o(n*logn)的演算法。
sa :將所有字尾串按字典序從小到大的一張成績單,如sa[0]表示第0名字尾串是suffix(sa[0]);
c : c[k]記錄字元k出現的次數
temp1, temp2:臨時儲存空間,借給*x和*y。
倍增加上基數排序的思想。
基數排序:比如對乙個數列進行排序,我們可以先按個位數排好,再按十位數排好,這就是基數排序。實際上我們是把十位數看做第一關鍵字,各位數看做第二關鍵字
倍增:初始時我們把每個字元看做長度為1的串,然後它和它的後乙個串組合為乙個二元組,按照基數排序對所有二元組排序,排完後,所有長度為2的串在成績單上有序了(注意suffix(n-1)沒有第二元,視為0,即最小)。同理再把長度2倍增為4,思考一下。
關於這個倍增+基數排序的過程難以用語言描述,在**中以注釋講解。
int get_sa(char *s,int n) //n是字串s長度
if(p>=n)break; //已完全離散化
m=p;}}
【rank和height】
僅僅有了sa陣列,我們還是做不了大事。
height:height[i]表示成績單上第i名與其前一名的最長公共字首,很明顯第0名必為0;
rank: sa的反函式,記錄suffix(i)是第幾名。
這兩個陣列好求:
void get_height(char *s,int n)
int j=sa[ranks[i]-1]; //j是i的前一名
while(s[i+k]==s[j+k])k++;
height[ranks[i]]=k;}}
有了height陣列,這個功能比較強大,我們可以求任意兩個字尾的最長公共字首,即lcp,求法:用rmq維護height陣列的區間最小值,若要訪問suffix(i)與suffix(j)的lcp,即為中的最小值。如果不好理解,在字尾樹模擬一下就明白了。
【多餘的解釋】
推薦一篇大佬的博文:
博文中與劉汝佳那本訓練指南上講的基本一致,但經過實踐我發現了若干錯誤。博文中有劉汝佳源**。
build函式倒數第四行:
for (int i=1;i這裡相當於對排好序的成績單做乙個離散化,t2[sa[i-1]]==t2[sa[i]] 用來判斷第一關鍵字,沒有問題。 t2[sa[i-1]+k]==t2[sa[i]+k])用來判斷第二關鍵字,這裡有問題!首先陣列越界是必然的,越界也沒關係,陣列開兩倍,後面越界了都是0。但是,成績單是從第0名開始!!故x陣列必有一項為0!!比如sa[i-1]+k取的那一項恰好t2[sa[i-1]+k]==0,而sa[i]+k>=n,必然t2[sa[i]+k]==0,顯然一定滿足相等,而實際上這裡的第二關鍵字是不相等的(前者為0,後者不存在,故不能認為相等)!
避免此bug的方法:1.排名從1開始就可以。2. 單獨判斷第二關鍵字,不要為了少些**,通過越界省事。3.。。。。。
宣告:我並沒有否定劉汝佳前輩的意思,思路完美++,只是這個bug困擾了我乙個上午。
【例題 spoj subst1 】
求乙個串中不同子串的個數。任意子串必為某一字尾串的字首,某一子串有重複只能是與字典序最接近的子串。
故重複串必出現在每兩個字典序相鄰的字尾串的最長公共字首,即lcp。
任意字尾串所包含的子串總個數 = n - sa[i] ,注意下標i是這個字尾串的排名
去掉重複的就是 :n - sa[i] - height[i] ;
求總和。
【**】:
【例題 2018icpc焦作網路賽 h string and times】
題意:給出乙個字串,兩個整數a,b,求有多少個子串滿足出現次數在[a,b]之間。
分析:先考慮如何求 >=x 的子串個數怎麼求,然後分別求出》=a和》=b+1的個數,相減即可。
求出現次數》=x的子串個數:看著字尾樹會好理解些。在成績單sa上,任選乙個起點 i,既然要求出現次數》=x,那麼從 i 到 i+x-1 的lcp(最長公共字首)的出現次數一定》=x,而且lcp的字首也都出現》=x次。故從成績單上取出所有的連續x個的lcp,但是會有重複,怎麼去重?當我查詢到 i -> i+x-1 的lcp累計到答案時,與i-1 -> i-1+x-1 重複了多少個呢?重複了 i-1 -> i+x-1 的lcp個,結合字尾樹仔細思考一下。
【**】:
【例題 poj3261】
題意:給出n個數的序列,求出現次數至少為k的最長連續子串行長度。
分析:焦作網路賽那個h題求的是出現至少k次的個數,這裡求的最長長度,注意區分。在成績單sa上列舉起點 i ,終點為i+k-1,取一下lcp,最終的lcp最大值即為答案。
【**】:
【例題 poj1743】
題意:給出n個數的序列,求乙個最長連續子串行,滿足:出現至少兩次,長度至少5次,不可以重疊,相似視為同乙個。兩個序列相似定義為 兩個序列的間隔相等,例如1,2,3,5,6與4,5,6,8,9相似。
分析:求出原序列的n-1個間隔構成的序列,求其字尾陣列。然後二分答案,對於答案len,判斷其是否可行:在height陣列找到一段連續的區間滿足 區間長度》=len 且 其中對應的sa最大值與最小值只差》len(為了產生不重疊);
【**】:
字尾陣列學習筆記
要用好字尾陣列要先理解裡面幾個陣列的概念 sa i 表示字典序第i大的字尾下標 字典序排名依次是1 len stri ng ra nk i 表示下標為i的字尾字典序排名 he ight i 表示sa i 和sa i 1 最長公共字首的長度.乙個性質 lc p su ffix i suff ix j ...
字尾陣列 學習筆記
字尾陣列是處理字串的強有力的工具 在字串處理當中,字尾樹和字尾陣列都是非常有力的工具。其實字尾陣列是字尾樹的乙個非常精巧的替代品,它比字尾樹容易程式設計實現,能夠實現字尾樹的很多功能而時間複雜度也不太遜色,並且,它比字尾樹所占用的空間小很多。可以說,在資訊學競賽中字尾陣列比字尾樹要更為實用。我們定義...
字尾陣列 學習筆記
剛剛學完回文自動機 來學字尾陣列 一開始思路看得懂 但是 看不懂呀 一堆神仙 洛谷p3809 勿謂我,何強過者,炸哉!我們需要一種新的演算法 字尾陣列 首先,輸入字串 scanf s ch 1 n strlen ch 1 然後,按照題意 suffix sort ch for int i 1 i n ...