\(\\\)
介紹一些寫法和陣列的含義,首先要知道 字典序 。
加入我們提取出了 \(suffix(1)...suffix(len-1)\) ,將他們按照字典序從小到達排序。
顯然這兩個陣列可以在 \(o(n)\) 的時間內互相推出。
\(\\\)
由於博主太蒟並不會dc3,想看dc3的同志們可以溜了
\(\\\)
倍增構造法。
從小到大列舉 \(k\) ,每次按照字典序排序,每乙個字尾的長度為 \(2^k\) 的字首,直到沒有相同排名的為止。
若有的字尾不夠長就在後面補上:比當前串全字符集最小字元還要小的字元,結果顯然符合字典序的定義。
\(\\\)
如何確定長度為 \(2^k\) 的每乙個字尾對應字首的排名?
倍增。有點像數學歸納法的感覺。
首先我們顯然可以直接求出來 \(k=0\) 的答案。
然後對於乙個 \(k\) ,我們顯然已經完成了 \(k-1\) 部分的工作。
所以對於乙個長度為 \(2^k\) 的字首,它顯然可以由兩個長度為 \(2^\) 的字首拼成。
也就是說,我們可以把長度為 \(2^k\) 的字首,寫成兩個長度為 \(2^\) 的字首的有序二元組。
有乙個顯然的結論,因為長度 \(2^\) 的所有字首有序,所以我們對這些二元組排序法則可以寫成:
以前乙個長度為 \(2^\) 的字首的 \(rank\) 為第一關鍵字,以後乙個長度為 \(2^\) 的字首的 \(rank\) 為第二關鍵字排序。
對於此方法得到的順序,與將整個長度為 \(2^k\) 的字首字典序排序得到的順序,想一想發現是相同的,因為它符合字典序定義。
\(\\\)
比較到什麼時候為止?顯然是求到乙個 \(k\),使得每乙個字尾 \(rank\) 不同時。
下面重點說一下**實現,演算法的精華也就體現在這裡。附上乙個寫的不錯的部落格 。
\(\\\)
再次宣告一些陣列的定義:
\(\\\)
第一步,將長度為 \(1\) 的每乙個字元排序。
這個過程就是基數排序。過程中的 \(n\) 表示陣列長度,\(m\) 表示原串字符集範圍為 \([1,m-1]\) 。
注意體會最後一行的倒序迴圈,此時體現了 \(rank\) 相同時按照第乙個字元在字串出現的位置排序的原則。
for(r int i=0;i\(\\\)
然後我們就要開始倍增構造,設 \(k\) 直接表示當前考慮的字首長度。
for(r int k=1,p=0;k<=n;k<<=1)
\(\\\)
首先看本次排序構造的 \(y[i]\) 。
由於 \(sa\) 陣列是有序的,所以我們沒必要對 \(y[i]\) 陣列進行一次基數排序。
p=0;
for(r int i=n-k;i=k) y[p++]=sa[i]-k;
第二行的含義是,因為字串的後 \(k\) 個字尾一定不能再找到長度為 \(k\) 的字尾繼續拼接了。
根據字典序的定義,空串字典序優於任何乙個字串,所以他們的 \(y\) 應該最靠前。
同時因為 \(rank\) 相同時按照第乙個字元在字串出現的位置排序的原則,迴圈是正序。
第三行的含義是,如果乙個長度為 \(k\) 的字首起始位置 \(\le k\) ,那它必然作為乙個後一段接在前面的某乙個位置上。
可以注意到的是, \(sa\) 陣列和 \(y\) 陣列的定義形式是一致的,也就是說, 我們按照 \(sa\) 的順序構造 \(y\) 沒有問題。
\(\\\)
然後就要構造 \(sa[i]\) 。這也是構造過程中最精華的一部分。
for(r int i=0;i這其實是乙個雙關鍵字基數排序的過程。
雙關鍵字基數排序時,我們需要先將第二關鍵字直接排序,然後再使用上面的**。
現在 \(y[i]\) 顯然已經是有序的了。
這個過程的理解可以參考最開始的單關鍵字基數排序。
為什麼那時我們做到了在 \(rank\) 相同時我們按照第乙個字元在字串出現的位置從小到大排序的要求?
因為我們是倒著掃瞄的。
同理,為了讓 \(x\) 相同的 \(y\) 越劣的越靠後,我們直接倒著掃瞄 \(y\) 不就可以了嗎!
此時我們成功在 \(sa\) 陣列內完成了第一第二關鍵字合併後的排序。
\(\\\)
然後要做的就是還原 \(rank\) 陣列了。
注意 \(rank\) 陣列的定義中可以有相同的排名,所以第一第二關鍵字 \(rank\) 相同的注意要特殊對待。
inline bool cmp(int *a,int x,int y,int k)
swap(x,y); p=1; x[sa[0]]=0;
for(r int i=1;i注意這個指標交換的過程,它優化掉了 \(swap\) 兩個陣列的複雜度。
因為 \(x\) 陣列是上乙個 \(k\) 的 \(rank\) 結果,所以可以直接比對新的即將拼合的兩段是否相同。
\(\\\)
最後還有乙個小優化。
if(p>=n) break;
m=p;
就是 \(p=n\) 時,可以發現當前長度的字首已經具有了區分每乙個字尾的作用,所以我們沒必要繼續比下去了。
\(\\\)
最後再多說一句,值得注意的是,不管是哪種實現方式,除了空字元外 \(rank\) 必須從 1 開始,否則會造成最小字元與空字元執行時混淆。
\(\\\)
給出乙個字串,寫出其所有迴圈同構串,將其按字典序從小大排序,輸出排序後每乙個串的尾字元。
\(\\\)
環的問題一般可以破環成鏈去搞。
拆開之後複製一倍接在後面,直接跑字尾陣列,按 \(sa\) 順序輸出所有長度大於 \(len\) 的字尾對應答案。
\(\\\)
#include#include#include#include#include#include#include#define n 200005
#define r register
using namespace std;
char ss[n];
int s[n],sa[n],cnt[n],t1[n],t2[n];
void da(int n,int m)
--n;
for(r int i=0;i
}int main()
Suffix Array 字尾陣列
顧名思義,suffixarray 以下有時簡稱sa 和字串的字尾有關。字尾 字串中某個位置一直到結尾的子串。sa中討論包括了原串和空串 所以共有len 1個字尾。字尾陣列 字串的所有字尾組成的按字典序從小到大排好的陣列。由於sa中記錄的都是字串的字尾,所以sa只需要記錄其表示的字尾的起始位置。由於比...
字尾陣列(Suffix Array)
字尾陣列是處理字串的有力工具。sa儲存乙個字串按字典序排列的字尾,如圖 rank陣列儲存字尾i的名次,就是把sa反過來,上圖中 rank 1 2,rank 2 8 height陣列儲存相鄰兩個sa字尾之間公共字首的長度,如圖 思路 用倍增的方法對每個字元開始的長度為2 k2 k 2k子字串進行排序,...
字尾陣列suffix array
倍增演算法,時間複雜度o nlogn sa從小到大儲存相對大小的下標 理解lsd,x陣列,sa陣列 char s maxn int sa maxn t maxn t2 maxn c maxn n void build sa int m void build sa int m int cmp suff...