1. 程式人生 > 實用技巧 >字尾陣列(模板)

字尾陣列(模板)

介紹

字尾陣列就是字串的每個字尾的排序。
主要有兩個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;

參考