[补档计划] 后缀数组

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[补档计划] 后缀数组相关的知识,希望对你有一定的参考价值。

后缀数组的实现

  对于一个字符串 S , 有后缀数组 sa[1..n] , 排名数组 rk[1..n], 和辅助数组 height[1..n].

    sa[i]: 在 S 的后缀中, 排名第 i 的后缀为 S[sa[i]: n] .

    rk[i]: 在 S 的后缀中, S[i:n] 的排名.

    height[i]: S[sa[i-1]:n] 与 S[sa[i]:n] 的 LCP.

    显然有 rk[sa[i]] = i, sa[rk[i]] = i.

  使用倍增的方法快速求 sa[1..n] 和 rk[1..n] .

  求 ht[1..n] 的时候, 我们依次处理 S[1:n], S[2:n], ..., S[n:n], 处理 S[i:n] 的时候求 ht[rk[i]] . Brute Force 的复杂度为 $O(n^2)$ , 但是我们可以利用一个性质: $ht[rk[i]] \\ge ht[rk[i-1]]-1$ , 附一个无字证明:

    技术分享

#include <cstdio>
#include <cstring>
#include <cstdlib>

#define F(i, a, b) for (register int i = (a); i <= (b); i++)
#define D(i, a, b) for (register int i = (a); i >= (b); i--)

const int N = 50005;

char s[N]; int n;
int rk[N], sa[N], ht[N];

namespace Output {
    const int S = 1000000; char s[S]; char *t = s;
    inline void Print(int x) {
        if (x == 0) *t++ = 0;
        else {
            static int a[65]; int n = 0;
            for (; x > 0; x /= 10) a[++n] = x%10;
            while (n > 0) *t++ = 0+a[n--];
        }
        *t++ =  ;
    }
    inline void Flush(void) { fwrite(s, 1, t-s, stdout); }
}
using Output::Print;

void Prework(void) {
    static int sum[N], trk[N], tsa[N]; int m = 500;
    F(i, 1, n) sum[rk[i] = s[i]]++;
    F(i, 1, m) sum[i] += sum[i-1];
    D(i, n, 1) sa[sum[rk[i]]--] = i;
    rk[sa[1]] = m = 1;

    F(i, 2, n) rk[sa[i]] = (s[sa[i]] != s[sa[i-1]] ? ++m : m);
    for (int j = 1; m != n; j <<= 1) {
        int p = 0; F(i, n-j+1, n) tsa[++p] = i; F(i, 1, n) if (sa[i] > j) tsa[++p] = sa[i]-j;
        F(i, 1, n) sum[i] = 0, trk[i] = rk[i];
        F(i, 1, n) sum[rk[i]]++;
        F(i, 1, m) sum[i] += sum[i-1];
        D(i, n, 1) sa[sum[trk[tsa[i]]]--] = tsa[i];
        rk[sa[1]] = m = 1;
        F(i, 2, n) {
            if (trk[sa[i]] != trk[sa[i-1]] || trk[sa[i]+j] != trk[sa[i-1]+j]) m++;
            rk[sa[i]] = m;
        }
    }

    m = 0;
    F(i, 1, n) {
        if (m > 0) m--;
        while (s[i+m] == s[sa[rk[i]-1]+m]) m++;
        ht[rk[i]] = m;
    }
}

int main(void) {
    #ifndef ONLINE_JUDGE
        freopen("xsy1621.in", "r", stdin);
        freopen("xsy1621.out", "w", stdout);
    #endif

    scanf("%s", s+1); n = strlen(s+1);

    Prework();

    F(i, 1, n) Print(rk[i]); *(Output::t++) = \\n;
    F(i, 1, n) Print(ht[i]); *(Output::t++) = \\n;
    Output::Flush();

    return 0;
}

 

以上是关于[补档计划] 后缀数组的主要内容,如果未能解决你的问题,请参考以下文章

[补档题解]后缀树节点数

[补档计划] 覆盖子串与循环节

[补档计划] 字符串

[补档计划] 数位dp

[补档计划] 树4 - 线段树

[补档计划] 树2 - 树上倍增