【筆記】字尾陣列
time: 2021-12-09 14:13:13
tags:
- 筆記 字串/字尾陣列
題解 P3809 【【模板】字尾排序】 - xMinh 的部落格 - 洛谷部落格 (luogu.com.cn)
記號:
\(s_i\):字串 \(s\) 的第 \(i\) 個字尾。
\(t_i\):排好序後第 \(i\) 個字串。
\(LCP(s_i,s_j)\) 或 \(LCP(i,j)\):字串 \(s\) 第 \(i\) 個和第 \(j\) 個字尾的 LCP 長度。
\(rk\):原陣列到排好序的陣列的對映。
\(sa\):排好序的字尾陣列到原陣列的對映。
\(height_i\):\(LCP(h_{i-1},h_i)\)
\(h_i\):\(height[sa[i]]\)
LCP lemma
\[LCP(s_i,s_j)=\min_{k=\min(rk[i],rk[j])+1}^{\max(rk[i],rk[j])}(height_k) \]首先證 \(RHS\ge LHS\),也就是相鄰兩個的 LCP 必然大於等於 \(s_i\) 和 \(s_j\) 的 LCP。
abcd
abcx
abcd
abcd
考慮上面的情形,abcx
與前後的 LCP 小於 4,於是它不可能在字典序中出現在該位置。
然後證存在 \(height_k=LCP(s_i,s_j)\),因為如果所有的 \(height_k\) 都大於 \(LCP(s_i,s_j)\)
abcde
abcde
abcde
...
abcde
「相似度」引理
\[LCP(s_1,s_i)\le LCP(s_2,s_i)\le\dots \le LCP(s_{i-1},s_i) \]同理,有
\[LCP(s_i,s_{i+1})\ge LCP(s_i,s_{i+2})\ge\dots\ge LCP(s_i,s_n) \]abcdex
abcxxx
abcdef
反證法,如果存在 \(k<j\) 使得 \(LCP(s[rk[k]],s[rk[i]])>LCP(rk[j],rk[i])\),那麼它必然在字典序中 \(i\)
\(h[i]\ge h[i-1]-1\)
考慮第 \(i-1\) 個字尾在排好序陣列中前一個字串 \(k\),有
\[LCP(i,k+1)=\begin{cases} 0,&LCP(i-1,k)=0\\ LCP(i-1,k)-1,&LCP(i-1,k)>0 \end{cases} \]總之有 \(LCP(i,k+1)\ge h_{i-1}-1\). 考慮到 \(k\) 在字典序彙總排在 \(i-1\) 的前面,而 \(i\) 和 \(k+1\) 由 \(i-1\) 和 \(k\) 去掉首字元得到,所以 \(k+1\) 在字典序中也排在 \(i\) 的前面。由「相似度」引理知 \(h_i=LCP(i,sa[rk[i]-1]])\ge LCP(i,k+1)\ge h_{i-1}-1\).
於是按照 \(i=rk[1],rk[2],\dots,rk[n]\) 的順序計算 \(h_i\),便可做到 \(O(n)\) 複雜度求 height.
程式碼
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
int n, m, cnt, t[N], sa[N], rk[2 * N], l[2 * N];
char c[N];
void buildSA() {
m = (int)'z';
for(int i = 1; i <= n; i++) ++t[rk[i] = c[i]];
for(int i = 1; i <= m; i++) t[i] += t[i - 1];
for(int i = n; i >= 1; i--) sa[t[rk[i]]--] = i;
for(int k = 1; k < n; k <<= 1) {
// 按第二關鍵字排序
for(int i = n - k + 1; i <= n; i++) l[cnt = i - n + k] = i;
for(int i = 1; i <= n; i++)
if(sa[i] > k) l[++cnt] = sa[i] - k;
// 按第一關鍵字排序
for(int i = 1; i <= m; i++) t[i] = 0;
for(int i = 1; i <= n; i++) ++t[rk[i]];
for(int i = 1; i <= m; i++) t[i] += t[i - 1];
for(int i = n; i >= 1; i--) sa[t[rk[l[i]]]--] = l[i], l[i] = 0;
// 處理新的 rk
swap(rk, l), cnt = 0;
for(int i = 1; i <= n; i++)
if(l[sa[i]] == l[sa[i - 1]] && l[sa[i] + k] == l[sa[i - 1] + k]) rk[sa[i]] = cnt;
else rk[sa[i]] = ++cnt;
if((m = cnt) == n) break;
}
// 處理最終的 rk
for(int i = 1; i <= n; i++) rk[sa[i]] = i;
}
void getHeight() {
for(int i = 1, k = 0; i <= n; i++) {
if(rk[i] == 1) continue;
if(k) --k;
for(int j = sa[rk[i] - 1]; j + k <= n && i + k <= n && c[i + k] == c[j + k]; ++k);
height[rk[i]] = k;
}
}
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
scanf("%s", c + 1);
n = strlen(c + 1);
buildSA();
getHeight();
for(int i = 1; i <= n; i++) cout << sa[i] << ' ';
return 0;
}