考慮乙個問題:
給出乙個長度為 \(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演算法複雜一點,但...