字尾陣列 SA

2022-05-12 08:33:35 字數 1709 閱讀 7669

考慮乙個問題:

給出乙個長度為 \(n\) 的字串,容易知道其一共有 \(n\) 個字尾,現將這 \(n\) 個字尾進行排序,求出排序後的字尾序列。

這裡給出一些定義:

子串:字串 \(s\) 中連續的一段字元,從第 \(i\) 個字元到第 \(j\) 個字元的子串記作 \(s[i\dots j]\) ,其表示的字串為 \(s_i,s_,s_,\dots,s_,s_j\) ,長度為 \(j-i+1\) 。

字尾:指右端點與原串右端點重合的子串,子串 \(s[i\dots n]\) 記作 \(\text\) 。

關於兩個字串的大小,我們從兩個字串的第乙個字元開始逐個比較,設當前比較到第 \(i\) 個字元。若 \(s1[i]>s2[i]\) ,則 \(s1>s2\) ;若當其中較短串全都比較完畢並無不同,那麼看做較長的字串較大。如若長度全都相等,則視為兩個字串相等。

考慮將字尾進行排序的問題,顯然各個字串的長度都不相同,可知每個字尾都有唯一切確定的排名。

樸素做法:

對於樸素做法,我們可以使用快速排序 \(\left(sort\right)\) 來進行處理,對於兩個字串的比較,我們進行 \(\mathcal\left(n\right)\) 複雜度的比較,可以得到乙個 \(\mathcal\left(n^2logn\right)\) 的演算法。

不用說,這個複雜度顯然不夠優秀。

亂搞做法

我們可以想辦法優化一下字串比較,實際上對於兩個字串,我們可以將他們分為兩部分,分別是前一段的相同區域以及後一段的不同區域。我們可以二分找出兩個區域的分界處,使用雜湊處理即可,複雜度是 \(\mathcal\left(nlog^2n\right)\) 的。

倍增做法

我們使用倍增的方法比較字串的大小,並使用基數排序的方法,最後可以達到乙個比較優秀的複雜度 \(\mathcal\left(nlogn\right)\) 。

我們的主體思路是我們將從每乙個字元開始長度為 \(2^k\) 的字串進行排序,列舉 \(k\) 將字串長度進行倍增,最後得到所有字尾的排序。

我們考慮基數排序,每個元素有兩個關鍵字,並且關鍵字的值域並不大,可以使用桶來處理。我們首先列舉第二關鍵字,再列舉第一關鍵字,然後我們驚奇的發現排序已經完成了,演算法的複雜度是 \(\mathcal\left(n\right)\) 的。

對於每次排序的兩個關鍵字,都可以從上一次的排序結果裡獲取。

#include #include #include using namespace std;

const int n=1e6+7;

char s[n];

int n,rk[n],tp[n],m;

int buc[n],sa[n],h[n];

void qsort()

int main()

} for(int i = 1;i <= n;i ++) printf("%d ",sa[i]);

puts("");

for(int i = 1,p = 0;i <= n;h[rk[i++]] = p)

for(int i = 2;i <= n;i ++) printf("%d ",h[i]);

return 0;

}

SA 字尾陣列

首先一定要確定sa 是個什麼東西 sa i 表示的是排名為 i 的字尾是哪乙個 至於字尾 i的排名是多少,那個是ra nk i 當然啦 最最最難懂的就是基數排序 要是不用基數排序,每次對於乙個二元組直接so rt一下 這樣的複雜度是o nlog 2 對於二元組的基數排序應該是這樣做的 首先把所有元素...

字尾陣列SA

給定乙個字串s,按字典序排序s的所有子串 鬼知道什麼思想,好像沒有什麼思想。哦,想起來了,是倍增。考慮最簡單的字尾間o n o n 比較和快排o nlog n o n logn 總複雜度o n2lo gn o n 2log n 考慮優化字串間的比較,用倍增的思想,假設k 2 k 2 長度的已經比完了...

字尾陣列SA

原理 其本質就是把字串的所有字尾進行排序。用普通排序需要o nlogn 但是字串比較和數字比較不同,所以實際需要o n nlogn 為了讓這個過程快一點,所以有了倍增演算法,o nlogn 和dc3演算法,o n 倍增演算法比較簡單,也比較好寫,具體可以參考這個大佬的部落格。dc3演算法複雜一點,但...