[演算法學習] 字尾排序
阿新 • • 發佈:2020-08-09
字尾排序
\(sa_i\) 表示字尾排名為\(i\)的位置。
\(rk_i\) 表示以\(i\)為首的字尾在所有後綴的排名。
\(LCP(i,j)\)表示\(sa_i\)和\(sa_j\)的最長字首的長度
\(height_i = LCP(i-1, i)\)
\(LCP(i,i) = len(sa[i]) = n - sa[i] + 1\)
\(LCP(i,j) = min(LCP(k - 1, k)) k \in [i + 1, j]\)
排序思想
利用倍增的思想。
定義\(s[i][k]\)表示以\(i\)開頭的長度為\(2^k\)的字串 (如果字尾長度\(<2^k\) 就以空格補齊)
那麼如果要比較\(s[i][k+1]\)和\(s[j][k+1]\)
那麼就是要比較\(s[i][k] s[j][k]\) 如果相等
那麼就比較\(s[i+2^k][k] s[j+2^k][k]\)
\(\Theta(n \log^2 n)\) 做法
inline bool cmp(int i, int j) { if(rank[i] != rank[j]) return rank[i] < rank[j]; else { int ri = i + k < n ? rank[i + k] : -1; int rj = j + k < n ? rank[j + k] : -1; return ri < rj; } } int main() { scanf("%s", ch); n = strlen(ch); for(int i = 0; i < n; i++) { sa[i] = i; rank[i] = ch[i]; } for(k = 1; k < n; k <<= 1) { sort(sa, sa + n, cmp); tmp[sa[0]] = 0; for(int i = 1; i < n; i++) tmp[sa[i]] = tmp[sa[i - 1]] + (cmp(sa[i - 1], sa[i]) ? 1 : 0); for(int i = 0; i < n; i++) rank[i] = tmp[i]; } for(int i = 0; i < n; i++) printf("%d ", sa[i] + 1); return 0; }
利用基數排序可以直接優化到\(\Theta(n \log n)\)
struct SA { int n, m, x[N], y[N], sa[N], rk[N], buk[N], height[N]; char s[N]; inline void GET_SA() { for(int i = 1; i <= n; i++) x[i] = s[i], ++ buk[s[i]]; for(int i = 2; i <= m; i++) buk[i] += buk[i - 1]; for(int i = n; i >= 1; i--) sa[buk[x[i]] -- ] = i; for(int k = 1; k <= n; k <<= 1) { int num = 0; for(int i = n - k + 1; i <= n; i++) y[++num] = i; for(int i = 1; i <= n; i++) if(sa[i] > k) y[++num] = sa[i] - k; for(int i = 0; i <= m; i++) buk[i] = 0; for(int i = 1; i <= n; i++) ++ buk[x[i]]; for(int i = 2; i <= m; i++) buk[i] += buk[i - 1]; for(int i = n; i >= 1; i--) sa[buk[x[y[i]]]--] = y[i], y[i] = 0; swap(x, y); x[sa[1]] = 1; num = 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]) ? num : ++num; if(num == n) break; m = num; } for(int i = 1; i <= n; i++) printf("%d ", sa[i]); puts(""); } inline void GET_HEIGHT() { int k = 0; for(int i = 1; i <= n; i++) rk[sa[i]] = i; for(int i = 1; i <= n; i++) { if(rk[i] == 1) continue; if(k) k -- ; int j = sa[rk[i] - 1]; while(j + k <= n && i + k <= n && s[i + k] == s[j + k]) ++ k; height[rk[i]] = k; } for(int i = 1; i <= n; i++) printf("%d ", height[i]); puts(""); } } sa;