字尾陣列(模板)
阿新 • • 發佈:2020-07-16
介紹
字尾陣列就是字串的每個字尾的排序。
主要有兩個sa和rk兩個陣列,sa[i]代表第i大的字尾的位置,rk[i]代表位置i的字尾的排位。滿足rk[sa[i]] = sa[rk[i]] = i
實現
有很多求字尾陣列的方法,其中一種是倍增法。
先給字串每一位排序,然後倍增排序。假設當前倍增長度為\(2^k\),那麼對於位置i,以rk[i]為第一關鍵字,rk[i+\(2^k\)]為第二關鍵字排序。
時間複雜度O(n(logn)^2)。
偷個oiwiki的圖,倍增排序示意圖:
還有O(n)的複雜度的方法,有機會再補了。
const int N = 2e5 + 10; int sa[N], rk[N << 1], oldrk[N << 1]; //倍增要開兩倍空間,每次排序的格式化也要格式化兩倍空間(重要) char s[N]; int main() { IOS; cin >> s + 1; int n = strlen(s + 1); for(int i = 1; i <= n; i++) { rk[i] = s[i] - 'a' + 1; //由於rk初始化為0,所以一開始要從1開始 } for(int i = 1; i <= n; i++) sa[i] = i; for(int w = 1; w < n; w <<= 1) { for(int i = 1; i <= n; i++) sa[i] = i; sort(sa + 1, sa + n + 1, [w](int x, int y) {return rk[x] == rk[y] ? rk[x + w] < rk[y + w] : rk[x] < rk[y];}); for(int i = 0; i <= (n << 1); i++) oldrk[i] = rk[i]; //兩倍空間,拷貝要完整地拷貝 int p = 0; for(int i = 1; i <= n; i++) { if(oldrk[sa[i]] == oldrk[sa[i - 1]] && oldrk[sa[i] + w] == oldrk[sa[i - 1] + w]) { rk[sa[i]] = p; } else { rk[sa[i]] = ++p; } } } for(int i = 1; i <= n; i++) cout << sa[i] << " "; cout << endl; }
height陣列
\(height[i] = lcp(sa[i], sa[i-1])\), 即第i名的字尾與它前一名的字尾的最長公共字首。
具體就是使用引理\(height[rk[i]] \le height[rk[i-1]]-1\)來暴力求,時間複雜度O(n)。
int k = 0; for(int i = 1; i <= n; i++) { if(k) k--; while(i + k < n && s[i + k] == s[sa[rk[i] - 1] + k]) k++; ht[rk[i]] = k; } for(int i = 2; i <= n; i++) cout << ht[i] << " "; cout << endl;