后缀数组的构造
注:字符串下表从1开始。
后缀数组$sa$定义
$sa_i$表示字典序排名为$i$的后缀的起始位置。
思想
暴力使用快速排序对$n$个后缀进行排序的比较次数是$n \log n$,然而比较一次的时间复杂度为$O(n)$。导致总复杂度为$O(n^2 \log n)$。
$Manber$ & $Mayer$倍增法,基于每一次对每个后缀的前$2^k$个元素进行基数排序,复杂度$O(n \log n)$。其思想的中心在于,由于后缀与后缀之间不像普通的数字,我们要排序的对象本身是内部有联系的。每一次排完后缀的前$2^k$个元素之后要想排$2^{k+1}$个,不过是由两个$2^k$拼合而成的。而由于已对所有$2^k$进行了排序,也就知道了$2^k$的相对关系,这样也就将问题转化为了对二元组的排序。而这能够利用基数排序$O(n)$完成。
基数排序的思想就是利用第二关键字已经有序的情况下,对第一关键字排序。这样保证了对于每一个第一关键字,其第二关键字都是有序的。因此整个就排好序了。
/*DennyQi 2019*/ #include <cstdio> #include <algorithm> #include <cstring> #include <queue> using namespace std; const int N = 1000010; inline int read(){ int x(0),w(1); char c = getchar(); while(c^'-' && (c<'0' || c>'9')) c = getchar(); if(c=='-') w = -1, c = getchar(); while(c>='0' && c<='9') x = (x<<3)+(x<<1)+c-'0', c = getchar(); return x*w; } char s[N]; int n,x[N],y[N],c[N],sa[N]; inline void BuildSA(int m){ int p; for(int i = 1; i <= n; ++i) ++c[x[i] = s[i]]; for(int i = 1; i <= m; ++i) c[i] += c[i-1]; for(int i = n; i >= 1; --i) sa[c[x[i]]--] = i; for(int k = 1; k <= n; k += k){ p = 0; for(int i = n-k+1; i <= n; ++i) y[++p] = i; for(int i = 1; i <= n; ++i) if(sa[i] > k) y[++p] = sa[i]-k; for(int i = 1; i <= m; ++i) c[i] = 0; for(int i = 1; i <= n; ++i) ++c[x[i]]; for(int i = 1; i <= m; ++i) c[i] += c[i-1]; for(int i = n; i >= 1; --i) sa[c[x[y[i]]]--] = y[i], y[i] = 0; swap(x,y); x[sa[1]] = 1, p = 1; for(int i = 2; i <= n; ++i) x[sa[i]] = (y[sa[i]]==y[sa[i-1]] && y[sa[i]+k]==y[sa[i-1]+k]) ? p : ++p; if(p >= n) break; m = p; } } int main(){ // freopen(".in","r",stdin); scanf("%s",s+1); n = strlen(s+1), BuildSA('z'+1); for(int i = 1; i <= n; ++i) printf("%d ",sa[i]); return 0; }