學習筆記 字尾陣列

2022-04-07 10:01:57 字數 3586 閱讀 5833

\(lcp\)...只能二分加 \(hash\) 嗎...?

​ 為了描述方便, 我們把 "字尾 \(i\) " 定義為 : 以下標 \(i\) 為起點的字尾.

字尾陣列的主要過程, 實際上就是為了求兩個陣列.

\(rank[i]\) : 字尾 \(i\) 的排名.

\(sa[i]\) : 排名為 \(i\) 的字尾的編號.

​ 最暴力的想法 : 把 \(n\) 個字尾都扒下來, 排個序. ( stl string 真好用)

​ 我們發現, 任意兩個字尾都會有相同的部分, 那我們是否可以利用這一點來優化排序的過程.

​ 總思路 :倍增.

​ (為了更方便描述, 我們設 \(s(i,j)\) 為以下標 \(i\) 為起點, 且長度為 \(2^j\) 的字串, 長度不夠時在後面補乙個小於字符集中任意字元的元素).

​ 先弄出所有 \(s(i,0)\) 的排名, 然後據此得出所有 \(s(i,1)\) 的排名, 然後再據此得出所有 \(s(i,2)\) 的排名, 以此類推, 直到 \(2^j \ge |s|\) 為止.

大概過程 :

​ 我們設 \(rk[i][j]\) 為 \(s(i,j)\) 在當前階段的排名 (當前所有的字串長度都為 \(2^j\)).

​ 假設我們當前已經得到了\(rk[i][j-1]\), 我們現在想得到 \(rk[i][j]\).

​ 那麼, 我們需要對每個 \(s(i,j)\) 都建乙個排序用的 \(pair\).

​ \(pair[i]\) 的第一關鍵字是在長度為 \(2^\) 時, 以 \(i\) 為起點的字串的排名, 即 \(rk[i][j-1]\),

​ \(pair[i]\) 的第二關鍵字是在長度為 \(2^\) 時, 以 \(i+2^\) 為起點的字串的排名, 即 \(rk[i+2^][j]\).

​ 這樣設定的原因是 : \(s(i,j-1) + s(i+2^,j-1)\) 實際上就等於 \(s(i,j)\), 那麼我們用這兩個關鍵字排序, 就可以得到 \(rk[i][j]\).

​ 如果直接 \(sort\) 的話, 總複雜度為 \(o(n\log^2 n)\), 但是我們可以使用基數排序, 使得每次排序複雜度降低到 \(o(n)\), 總複雜度也就變為 \(o(n\log n)\).

基數排序要利用到兩次桶排, 大致過程如下,

先按照第二關鍵字桶排.

算出每個第一關鍵字所擁有的元素數量, 按照第二關鍵字從小到大地把元素放到第一關鍵字的桶的對應位置上.

**

int n,m=100,rk[_],sa[_],c[_],t[_];

// n: 字串長度 m: 字符集大小 rk[i]: 字串 i 排名 sa[i]: 排名為 i 的字串

// c[i]: 第一關鍵字為 i 的字串個數 t[i]: 以第二關鍵字排序的桶

void g_sa()

}

​ 求 \(lcp\). ( \(longest\ common\ prefix\) 最長公共字首)

​ 說是擴充套件應用, 但是字尾陣列基本上就是用來做這個的...

​ 我們引入乙個 \(height\) 陣列,

​ \(height[i]\) 表示 :排名為 \(i\)的字串與排名為 \(i-1\)

的字串的最長公共字首, 即 \(lcp(sa[i],sa[i-1])\).

​ 這東西是可以 \(o(n)\) 求的, 為了達到這個複雜度, 我們需要用到乙個引理,

\[height(rk[i]) \ge height(rk[i-1])-1

\]證明

​ 設字尾 \(i-1\) 與字尾 \(x\) 的 \(lcp\) 長度為 \(len\). 因為字尾 \(i\) 相當於把字尾 \(i-1\) 的第乙個字元去掉後得到的字串, 而字尾 \(x+1\) 也是把字尾 \(x\) 的第乙個字元去掉的後得到的字串, 所以字尾 \(i\) 與字尾 \(x+1\) 的 \(lcp\) 長度為 \(len-1\), 得證.

​ 這樣, 我們就可以列舉 \(i\), 並從 \(height(rk[i-1])-1\) 的位置開始, 將字串 \(i\) 和字串 \(sa[rk[i]-1]\) 逐位匹配.

**

int hgt[_];

void g_hgt()

}

​ 處理完 \(height\) 陣列後, 我們該怎麼求任意兩個陣列間的 \(lcp\) 呢?

​ 再來乙個引理

\[\begin

lcp(i,j) &= min \,\ ( rk[i] < rk[k] \le rk[j]) \\

&= min \,\ (rk[i] < t \le rk[j])

\end

\]證明

​ 設 \(t = min \ = len,\ (rk[i] < t \le rk[j])\), \(s\) 為原字串.

​ 首先證明上界.

​ 因為 \(rk[i] < rk[sa[t-1]] < rk[sa[t]] \le rk[j]\).

​ 所以 \(s[i+len] \le s[sa[t-1]+len] < s[sa[t]+len] \le sa[j+len]\).

​ 即 \(s[i+len] < s[j+len]\)

​ 所以 \(lcp(i,j) \le len\), 上界得證.

​ 再證明下界. 運用反證法.

​ 假設 \(lcp(i,j) < len\),

​ 那麼可以得到 \(s[i+len-1] < s[sa[t-1]+len-1] = s[sa[t]+len-1] < s[j+len-1]\),

​ 那麼一定會存在乙個 \(x \in (i,t-1] \cup (t,j]\), 滿足 \(s[sa[x-1]+len-1] < s[sa[x]+len-1]\), 即 \(height[x] < len < height[t]\), 與條件矛盾, 故假設不成立.

​ 所以 \(lcp(i,j) \ge len\), 下界得證

​ 綜上所述, \(lcp(i,j) = len\), 得證.

​ 那麼, 對於任意兩個字尾 \(i,j\ (i \le j)\), 我們可以求出滿足 \(rk[i] < t \le rk[j]\), 最小的 \(height[t]\), 這個最小值即是 \(lcp(i,j)\).

​ hihocoder 字尾陣列系列 (搜尋 "字尾陣列" 即可)

字尾陣列一 : 單調棧

字尾陣列二 : 二分答案

字尾陣列三 : 將兩個陣列拼接, 比較 \(sa\) 上相鄰兩個串的 \(lcp\).

​ 擴充套件 : [poi2000]公共串 : 將直接比較改為單調佇列即可.

字尾陣列四 : 列舉 \(l\), 每次把序列分成 \(\frac\) 個長度為 \(l\) 的塊, 查詢相鄰兩個塊首的 \(lcp\), 再根據 \(lcp\) 處理最優解 的起點在塊內的情況

​ oi wiki - 字尾陣列(sa)

​ 字尾陣列 學習筆記 by xminh

字尾陣列學習筆記

要用好字尾陣列要先理解裡面幾個陣列的概念 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 ...