后缀数组-SA

引入

给出一个长为 \(n\) 的字符串 \(s\),要求每个后缀的排名。

约定

后缀 \(i\) 表示 \(s_i\) 开头的后缀,储存时用 \(i\) 直接代表 \(s_{i...n}\)

\(sa_i\) 表示排名为 \(i\) 的后缀 \(x\)

\(rk_i\) 表示后缀 \(i\) 的排名。

\(O(n^2\log n)\)

将所有后缀都弄下来,然后直接用 sort 排序。

\(O(n\log^2 n)\)

这里需要用到倍增的思想。

我们先对长度为 \(1\) 的串进行排序,求出 \(sa,rk\)

然后用 \((rk_i,rk_{i+1})\) 作为排序的第一、二关键字去排序,就能求出长度为 \(2\) 的串的 \(sa,rk\)

再用用 \((rk_i,rk_{i+2})\) 作为排序的第一、二关键字去排序,就能求出长度为 \(4\) 的串的 \(sa,rk\)

以此类推,就可以得到最后的排名。

对于 \(i+l>n\) 的,我们将 \(rk_{i+l}=0\) 即可,因为空串显然是最小的。

\(O(n\log n)\)

发现我们每次都是两个关键字进行排序,可以考虑基数排序。

定义 \(tp_i\) 表示后半部分(即 \(s_{x+l/2...x+l-1}\))的排名为 \(i\) 的后缀 \(x\)

定义 \(cnt_i\) 表示当前排名 \(\le i\) 的串有多少个。(显然,如果每个串的排名都各不相同,那么就结束排序了)

过程:

  • 先计算 \(tp\)

  • 进行基数排序,利用 \(cnt\) 计算 \(sa\)

  • 最后利用上一次的 \(rk\) 和刚计算得到的 \(sa\) 更新 \(rk\)

先讲最后一步:

  • 显然 \(rk[sa_1]=1\)

  • 对于 \(sa_i\),如果上一次的 \(rk[sa_i]=rk[sa_{i-1}], rk[sa_i+l]=rk[sa_{i-1}+l]\),那么 \(rk[sa_i]=rk[sa_{i-1}]\);否则 \(rk[sa_i]=rk[sa_{i-1}]+1\)

再来讲讲基数排序:

  • \(rk\) 加到对应的 \(cnt_i\) 中,并对 \(cnt\) 做前缀和

  • 按照 \(tp\) 从大到小更新 \(sa\),因为对于相同的 \(rk\) (第一关键字),\(tp\) 越大,排名越靠后。每更新一个 \(sa\),将相应的 \(cnt\) 减一。

代码

后缀排序整体部分:

inline void sufsort()
{
    m = 75;
    FOR(i, 1, n) rk[i] = s[i] - '0' + 1, tp[i] = i;
    Bsort();
    for(int l = 1, cnt = 0; cnt < n; l <<= 1, m = cnt)
    {
        cnt = 0;
        FOR(i, n - l + 1, n) tp[++cnt] = i;
        FOR(i, 1, n) if(sa[i] > l) tp[++cnt] = sa[i] - l;
        Bsort();
        std::swap(rk, tp); rk[sa[1]] = cnt = 1;
        // 注意这时 tp 是之前长度为 la 时的 rk
        FOR(i, 2, n) rk[sa[i]] = (tp[sa[i]] == tp[sa[i - 1]] && tp[sa[i] + l] == tp[sa[i - 1] + l]) ? cnt : ++cnt;
    }
}

基数排序部分:

inline void Bsort()
{
    FOR(i, 1, m) tax[i] = 0;
    FOR(i, 1, n) tax[rk[i]]++;
    FOR(i, 1, m) tax[i] += tax[i - 1];
    ROF(i, n, 1) sa[tax[rk[tp[i]]]--] = tp[i];
}

height 数组

LCP:最长公共前缀。

\(height_i=\text {lcp}(sa_i, sa_{i-1})\)

现在要 \(O(n)\) 求出 \(height\) 数组。

定理:\(height[rk_i] \ge height[rk_{i-1}]-1\)

证明:(参考自 OI-Wiki)

  • 如果 \(height[rk_{i-1}]=1\),显然成立。

  • 如果 \(height[rk_{i-1}]>1\)

  • 说明 \(\text{lcp}(i-1,sa[rk_{i-1}-1])>1\)

  • 我们设 \(aA\) 来表示它俩的最长公共前缀,设后缀 \(i-1\)\(aAD\),后缀 \(sa[rk_{i-1}-1]\)\(aAB\)

  • 那么后缀 \(i\) 可以表示为 \(AD\),而且一定会存在后缀 \(sa[rk_{i-1}-1]+1\)\(AB\)

  • 因此 \(\text{lcp}(i,sa[rk_i-1])\) 至少为 \(A\)

  • 所以有 \(height[rk_i] \ge height[rk_{i-1}]-1\)

有了这个结论,我们就可以暴力但 \(O(n)\) 求出 \(height\) 数组了:

inline void get_hei()
{
    int k = 0;
    FOR(i, 1, n)
    {
        if(k) k--;
        while(s[i + k] == s[sa[rk[i] - 1] + k]) k++;
        hei[i] = k;
    }
}
posted @ 2022-10-25 09:50  zuytong  阅读(23)  评论(0编辑  收藏  举报