[补档计划] 后缀数组

后缀数组的实现

  对于一个字符串 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;
}
时间: 2024-08-24 07:39:12

[补档计划] 后缀数组的相关文章

[补档计划] 字符串 之 知识点汇总

学习一个算法, 需要弄清一些地方 ① 问题与算法的概念 ② 算法以及其思维轨迹 ③ 实现以及其思维轨迹 ④ 复杂度分析 ⑤ 应用 KMP算法 字符串匹配与KMP算法 为了方便弄清问题, 应该从特例入手. 设 A = " ababababb " , B = " ababa " , 我们要研究下面三个递进层次的字符串匹配问题: ① 是否存在 A 的子串等于 B ② 有几个 A 的子串等于 B ③ A 的哪些位置的子串等于 B KMP算法可以直接在线性复杂度解决问题③,

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

覆盖子串与循环节的概念 首先还是给覆盖子串和循环节下个定义. 为了方便后面的描述, 我们定义布尔记号 $P_S^T$ , 表示 $T$ 是否为 $S$ 的前缀. 对于一个定义, 为了对它有足够充分的了解, 可以通过多种形式描述这个定义, 这里就再引用图像的方法吧. 再引入前缀记号和后缀记号 pre[i] = S[1 : i], suf[i] = S[1 : |S|]. 对于字符串 S , 若字符串 T , 满足 $P_S^T$ , 且 $\exists k,~P_{kT}^S$ , 则称 T 为

[补档计划] 字符串

学习一个算法, 需要弄清一些地方: ① 问题与算法的概念; ② 算法, 思维轨迹 ③ 实现, 思维轨迹; ④ 复杂度分析; ⑤ 应用. KMP算法 字符串匹配与KMP算法 为了方便弄清问题, 应该从特例入手. 设 A = " ababababb " , B = " ababa " , 我们要研究下面三个递进层次的字符串匹配问题: ① 是否存在 A 的子串等于 B ② 有几个 A 的子串等于 B ③ A 的哪些位置的子串等于 B KMP算法可以直接在线性复杂度解决问题

[补档计划] SAM

对于一个算法或者数据结构的学习, 我们首先要弄清它的概念, 然后理解它的构建, 进而是实现和复杂度分析, 最后考虑如何应用它. 现在学习的是 SAM, Suffix Automaton, 后缀自动机. 推荐陈立杰的冬令营讲稿. https://wenku.baidu.com/view/90f22eec551810a6f4248606.html 什么是自动机? 有限状态自动机的功能是识别字符串. 令一个自动机 $A$ , 若 $A$ 能识别字符串 $S$ , 则 $A(S) = True$ ; 若

[补档计划] 类欧几里得算法

$$\begin{aligned} f(a, b, c, n) & = \sum_{i = 0}^n \lfloor \frac{ai + b}{c} \rfloor \\ & = \sum_{i = 0}^n \sum_{j = 0}^{m-1} [j < \lfloor \frac{ai + b}{c} \rfloor] \\ & = \sum_{i = 0}^n \sum_{j = 0}^{m-1} [j + 1 \le \lfloor \frac{ai + b}{c}

[补档计划] 概率论

4.1 事件与概率 在一个黑箱中, 放着 3 个红球和 1 个白球. 我们从箱中取出一个球, 再放回去, 反复进行若干次. 每一次的结果是不确定的, 但总体上拿到红球的次数与拿到白球的次数接近 3 : 1 . 我们发现, 这类现象很常见, 那么我们就要尝试把这类现象的特点进行概括, 命名, 然后研究它的性质, 进而应用它. 概括一下这种现象: 在个别实验中其结果呈现出不确定性, 而在大量重复实验中其结果又具有统计规律性. 为了简便地称呼这种现象, 我们要给它起名字, 称之为 "随机现象"

[补档计划] 树6 - 莫队算法

[CF633H] Fibonacci-ish II 题意 给定长度为 $N$ 个序列 $A = (a_1, a_2, ..., a_N)$ . $M$ 组询问 $(l, r)$ : 将 $a_l, a_{l+1}, ..., a_r$ 提取出来, 排序, 去重, 得到长度为 $K$ 的序列 $B = (b_1, b_2, ..., b_K)$ , 求 $\sum_{i = 1}^K f_ib_i$ . 其中 $f_i$ 为斐波那契数列的第 $i$ 项: $f_0 = f_1 = 1, f_i =

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

[CF787D] Legacy 题意 $N$ 个点, $M$ 条连边操作: $1~u~v~w~:~u\rightarrow v$ . $2~u~l~r~w~:~u\rightarrow [l, r]$ $3~u~l~r~w~:~[l, r]\rightarrow u$ . 给定点 $s$ , 求单源最短路. $N\le 10^5$ . 分析 考虑使用 线段树结构 优化 建图. 建立一棵线段树, 每个点表示一个区间. 拆点, 线段树的每个点拆成 入点 和 出点 . 出线段树 的儿子连父亲, 因为可

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

[SCOI2016] 萌萌哒 题意 求有多少个无前导零的 $N(N\le 10^5)$ 位数 $A$ , 满足 $M(M\le 10^5)$ 个限制条件 $L~R~X~Y$ : $A[L+i] = A[X+i]$ . 分析 区间的信息就先对 ST 表用并查集, 然后下传. 实现 #include <cstdio> #include <cstring> #include <cstdlib> #include <cctype> #include <cmat