后缀数组

参考:https://cp-algorithms.com/string/suffix-array.html

后缀数组指把一个字符串的所有后缀按照字典序排序得到的数组。

以下默认下标从 (0) 开始,令 S[i...] 为字符串从 i 开始的后缀。

sa[i]: 第 (i) 大的后缀的起始位置。

例:ababc 的后缀从小到大排序为:空后缀,ababc,abc,babc,bc,c,其后缀数组为 [5, 0, 2, 1, 3]。

lcp[i]:后缀数组中相邻两个后缀的最长公共前缀组成的数组,S[sa[i]...] 和 S[sa[i+1]...] 的最长公共前缀为 lcp[i]。

sa 的计算

倍增计算,复杂度 (O(n lg^2 n))(O(n lg n)) (优化排序部分)。

const int MN = 400010;
int n, k;
int rsa[MN], tmp[MN];

bool cmp(int x, int y) {
    if (rsa[x] != rsa[y]) return rsa[x] < rsa[y];
    int rx = x + k <= n ? rsa[x + k] : -1;
    int ry = y + k <= n ? rsa[y + k] : -1;
    return rx < ry;
}

template <class S> vector<int> exec(const S &s) {
    n = int(s.size());
    vector<int> sa(n + 1);
    for (int i = 0; i <= n; i++) {
        sa[i] = i;
        rsa[i] = i < n ? s[i] : -1;
    }
    for (k = 1; k <= n; k *= 2) {
        sort(sa.begin(), sa.end(), cmp); // 用计数排序可以优化这部分
        tmp[sa[0]] = 0;
        for (int i = 1; i <= n; i++) {
            tmp[sa[i]] = tmp[sa[i - 1]] + int(cmp(sa[i - 1], sa[i]));
        }
        copy(tmp, tmp + n + 1, rsa);
    }
    return sa;
}

lcp 的计算

令 rank[i] 为后缀 i 在后缀数组的排名。

从位置为 (0) 的后缀开始从前往后依次计算 S[sa[rank[i]-1]...] 与 S[i...] 的 lcp。

计 k=sa[rank[i]-1],假设已经求得了 S[k...] 和 S[i...] 的 lcp 为 L,那么 S[k+1...] 和 S[i+1...] 的 lcp 一定不小于 L-1,而 S[sa[rank[i+1]-1]...] 与 S[i+1...] 的 lcp 应该还要比这个更长,于是可以用一个类似双指针的方法 (O(n)) 计算所有 lcp[i]。

lcp 数组可以与 Sparse Table 配合使用,要计算后缀数组内任意两个串 i 和 j 的最长公共前缀,只需要计算 lcp[rank[i]],lcp[rank[i]+1],...,lcp[rank[j]-1] 的最小值。

原文地址:https://www.cnblogs.com/hfccccccccccccc/p/10218160.html