什麼是尾碼:一個字串從某位開始到其末尾的子串。
什麼是尾碼數組:一個字元的所有尾碼從小到大排列所形成的的數組。
尾碼數組可以解決很多的字串問題,本文主要寫的是構造尾碼數組的倍增演算法。
暴力構造:直接把n個尾碼排序。因為比較字串需要 O(n) O(n)的時間,所以總複雜度 O(n2log2n) O(n^2log_2n)。
這樣完全沒有利用到n個元素都是某一字串的尾碼這個特性,如何改進呢。
倍增演算法的思想就是比較所有尾碼的首碼,並倍增增加當前考察的前置長度。
具體來說:
假設我們已經對所有尾碼只取前k位並排好序,現在我們要把長度增加到k*2。可以發現對於一個尾碼i (s[i~n]),他的前k*2位可以分成兩部分:
尾碼i的前k位 與 尾碼i+k的前k位。
考慮比較兩個長度同為k*2的字串:若前k位大小不同就能直接出結果,否則就比較後k位的大小。
這些首碼的排名在之前已經得到了,我們把尾碼i的前k位作為第一關鍵字,尾碼i+k的前k位作為第二關鍵字,這樣是不是就可以比較大小了呢。
結合下面這張圖會好理解一些:(盜來的)
因為關鍵字其實就是上一次的排名,範圍只有1~N,所以我們可以用基數排序的方法O(n)得出當前答案。這樣總複雜度為 O(nlog2n) O(nlog_2n),很優秀。
下面是我的模板,相比dalao們的繁瑣一點,常數大一些,但是可讀性還是很強的,不容易寫錯。
#include<cstdio>#include<vector>#include<cmath>#include<cstring>#include<algorithm>using namespace std;const int maxn=500005;int n,rk[maxn],sa[maxn],tem[maxn],res[maxn];vector <int> tab[maxn];char s[maxn];void csort(int k){ int m=max(n,400); for(int i=0;i<=m;i++) tab[i].clear(); for(int i=1;i<=n;i++) tab[rk[tem[i]+k]].push_back(tem[i]); tem[0]=0; for(int i=0;i<=m;i++){ int len=tab[i].size(); for(int j=0;j<=len-1;j++) tem[++tem[0]]=tab[i][j]; }}void get_SA(){ for(int i=1;i<=n;i++) rk[i]=s[i]; for(int k=1;k<n;k<<=1){ for(int i=1;i<=n;i++) tem[i]=i; csort(k); csort(0); int now=0; tem[0]=0; for(int i=1;i<=n;i++){ if(!(rk[tem[i]]==rk[tem[i-1]]&&rk[tem[i]+k]==rk[tem[i-1]+k])) now++; res[tem[i]]=now; } for(int i=1;i<=n;i++) rk[i]=res[i]; } for(int i=1;i<=n;i++) sa[rk[i]]=i; //for(int i=1;i<=n;i++) printf("%s\n",s+sa[i]); printf("\n");}int main(){ freopen("sa.in","r",stdin); freopen("sa.out","w",stdout); scanf("%s",s+1); n=strlen(s+1); get_SA(); return 0;}