字尾陣列小結(markdown版)

2021-07-09 11:50:35 字數 3637 閱讀 8118

1.子串(注:串!=字串):字串 s 的子串r[i..j] ,i ≤ j,表示r 串中從 i 到 j 這一段,就是順次排列r[i],r[i+1],…,r[j] 形成的子串。

2.字尾:字尾是指從某個位置 i 開始到整個串末尾結束的乙個特殊子串。字串r 的從 第 i 個字 符 開 始 的 後 綴 表 示 為 suffix(i) ,也 就 是suffix(i)=r[i..len(r)] 。

3.字尾陣列(sa[i]存放排名第i大的子串首字元下標):字尾陣列 sa 是乙個一維陣列,它儲存1..n 的某個排列 sa1,sa[2], ……,sa[n],並且保證suffix(sa[i]) < suffix(sa[i+1]),1 ≤ i < n。也就是將 s 的 n 個字尾從小到大進行排序之後把排好序的字尾的開頭位置順次放入sa 中。

4.名次陣列(rank[i]存放suffix(i)的優先順序):名次陣列 rank[i] 儲存的是 suffix(i) 在所有字尾中從小到大排列的 「 名次 」 。

注:這個是排序的關鍵字~(這句話是我們排序的重點)

目標是求得串的sa陣列和rank陣列:易知sa和rank互為逆操作,即sa[rank[i]] = i;rank[sa[i]] = i;(所以我們只要求得其一,就能o(n)算出另乙個)

注:這個結論只在最後完成排序的時候符合。但sa和rank的定義一直都是適用的。原因是最後的時候不會存在相同(rank相等)的兩個子串。

1.設排序的的當前長度是h。suffix(i,h) 表示suffix(i)前h個字元(大於length會截斷)。

2.先按h=1,對suffix(i,h)(0 < i < s.length)排序。

3.倍增長度h,利用之前排序h/2長度後得到的rank陣列作為關鍵字,把後h/2部分作為第二關鍵字,把前h/2部分作為第一關鍵字,對h長度的子串作排序。

4.由於是倍增長度,所以最多作logn次排序。

注:那麼複雜要做到nlogn,顯然排序要o(n),o(n)一般都選計數排序。

以下內容請按**手動模擬乙個串abab的構造過程,求sa陣列。這能讓你真正清楚字尾陣列實現的精妙所在。

按h=1進行計數排序

// cnt是計數排序的輔助陣列,k是第一關鍵字,id是第二關鍵字下標陣列,r是以下標為第二關鍵字的新構陣列,w存放的是字串資訊。sa儲存的是排第i的是誰,

// #define rep(i,n) for(int i = 0;i < n; i++) ,

int *k = rk,*id = height,*r = res, *cnt = wa;//計數排序

rep(i,up) cnt[i] = 0;

rep(i,len) cnt[k[i] = w[i]]++;

rep(i,up) cnt[i+1] += cnt[i];

for(int i = len - 1; i >= 0; i--)

求第二關鍵字(想想為什麼構造w陣列的時候末尾要加個0)

//cnt是計數排序的輔助陣列,k是第一關鍵字,id是第二關鍵字下標陣列,r是以下標為第二關鍵字的新構陣列,w存放的是字串資訊,sa儲存的是排第i的是誰。

for(int i = len - d; i < len; i++) id[p++] = i;

rep(i,len) if(sa[i] >= d) id[p++] = sa[i] - d; //id儲存了按後h/2排序的的序列,即排第i的後h/2的是原陣列中的那個

rep(i,len) r[i] = k[id[i]]; //構造新的排序陣列

對新陣列排序

//cnt是計數排序的輔助陣列,k是第一關鍵字,id是第二關鍵字下標陣列,r是以下標為第二關鍵字的新構陣列,w存放的是字串資訊,sa儲存的是排第i的是誰

rep(i,up) cnt[i] = 0;

rep(i,len) cnt[r[i]]++;

rep(i,up) cnt[i+1] += cnt[i];

for(int i = len - 1; i >= 0; i--)

得到新的關鍵字(即按h長度排序後的離散序列)

//cnt是計數排序的輔助陣列,k是第一關鍵字,id是第二關鍵字下標陣列,r是以下標為第二關鍵字的新構陣列,w存放的是字串資訊,sa儲存的是排第i的是誰

swap(k,r);

p = 0;

k[sa[0]] = p++;

rep(i,len-1)

重複以上最後可以得到sa陣列

sa和rank有什麼用?

height陣列!!

height[i] 表示sa[i]和sa[i-1]的最長字首,height的構造看**手推一定能弄懂,自己找題目試試。

模板為了短小精悍,所以盡量壓縮了**。

#define rep(i,n) for(int i = 0;i < n; i++)

using

namespace

std;

const

int size = 200005,inf = 1

<<30;

int rk[size],sa[size],height[size],w[size],wa[size],res[size];

void getsa (int len,int up)

int d = 1,p = 0;

while(p < len)

swap(k,r);

p = 0;

k[sa[0]] = p++;

rep(i,len-1)

if(p >= len) return ;

d *= 2,up = p, p = 0;

}}void getheight(int len)

height[rk[i]] = p;

p = max(0,p - 1);

}}int getsuffix(char s)

w[len++] = 0;

getsa(len,up+1);

getheight(len);

return len;

}

最後給出幾題練習題。

•poj 2774 –最長公共連續子串(入門題目)

•poj1743—最長不重疊重複子串(二分的判定要小心點,這題有點特別。)

•poj3294—出現次數超過一半的最長子串(判斷組中不同串出現次數的技巧很關鍵)

•poj3261—重複k次可重疊子串。(會了上面兩題,這題應該很簡單,可以試試用單調棧。)

•poj2758—字尾陣列+rmq(這題難度不在rmq,而在於寫**的能力和查詢的演算法實現。)

字尾陣列小結

搞了這麼多字尾陣列,寫個總結 其實羅穗賽的 裡已經都總結得很清楚了。我這裡對一些 的具體實現細節和一些要注意的地方做一些說明。字尾陣列很重要的三個陣列就是 rank,sa和height了 其中rank i 表示i這個字尾的排名,sa i 表示排在第i位的字尾的首字母位置,height i 表示排名第...

字尾陣列小結

一道模板題 字尾陣列 sa 是乙個比較強大的處理字串的演算法,是有關字串的比較基礎是嗎?演算法,所以必須掌握 實現主要有倍增和dc 3 而我太弱了只學了倍增 s 就是這個字串,長度為le nran k i 表示i到 len 這個字尾在所有字尾中的排名 sa i 表示排名為 i 的字尾的首字元下標 h...

字尾陣列小結

我為什麼要叫小結呢這明明就是個題解包啊 直接偷迪哥的就好辣 差異 大概是個板子,求出 height 陣列後直接單調棧即可 相似子串 首先 子串是字尾的字首 其次 每個字尾貢獻的本質不同子串的數量是 n i 1 he i 因為子串過多所以我們考慮運用上文性質來二分查排名 至於相似度 rmq 即可?sa...