zoukankan      html  css  js  c++  java
  • 后缀数组入门

    由于后缀数组完全可以被 后缀树 / 后缀自动机 所替代,因此这里涉及到的后缀数组的知识较浅。

    定义

    字符串 (S),定义其后缀数组 (sa) 为将所有后缀按照字典序排序后的开头下标构成的数组。

    相应地,定义 (rk_i) 为后缀 (S_{i, n}) 在后缀数组当中的排名。

    求解

    同样考虑使用增量法。

    但由于要求出所有后缀按照字典序排序后的数组,我们考虑对每一个后缀都进行增量。

    具体地,假设当前已经获得了所有后缀前 (i) 个字符构成的后缀子串的后缀数组,记作 (sa_{i, 1 sim n}),要得到 (sa_{i + 1, 1 sim n})

    那么我们可以按照 (sa_{i, 1 sim n}) 为第一关键字,每个后缀的第 (i + 1) 个位置上的字符为第二关键字进行排序。

    可以发现这样复杂度是 (mathcal{O(n ^ 2log n)}) 的,并不优秀。

    但可以发现每次我们可以不只增加一个字符进去比较,而是可以再增加长度为 (i) 的字符比较。

    也就是说,我们将每个后缀按照 (sa_{i, j}) 为第一关键字比较,(sa_{i, j + i}) 为第二关键字比较进行排序。

    不难发现这样是倍增进行计算的,复杂度降至 (mathcal{O(n log ^ 2n)})

    事实上,我们发现 (sa) 数组的值域是 (mathcal{O(n)}) 的,于是可以直接进行基数排序,复杂度又可降至 (mathcal{O(n log n)})

    基础应用

    寻找最小的循环移动位置

    例题:「JSOI2007」字符加密

    直接将原串复制一遍接到原串的后面,问题即变为后缀排序问题。

    在字符串中找子串

    • 开始给定文本串,在线输入所有模式串,求模式串在文本串当中的出现次数,保证所有字符串串长 (le 5 imes 10 ^ 5)

    对文本串建出后缀数组,文本串如果出现一定为某个后缀的一个前缀。

    因此在后缀数组 (sa) 中,文本串出现的位置一定是一段区间。

    我们二分得到这个区间的左右端点,(check) 直接暴力求两个串的 ( m LCP) 即可。

    复杂度 (mathcal{O}(|S| log |S| + sum |T| log |S|))

    从字符串首尾取字符最小化字典序

    例题:「USACO07DEC」Best Cow Line

    考虑模拟这个流程,假设当前选取到了区间 ([l, r])

    那么我们比较 (S_{l, r}) 及其反串字典序,若前者大则移 (l) 否则移 (r),这样显然不劣。

    于是问题就变为一个子串及其反串的字典序比较问题,我们将其放缩为 (S_{l, n}) 这个后缀和 (S_{1, r}) 这个前缀的反串的字典序比较问题。

    这样就非常简单了,我们将原串的反串加到原串后面,于是就变为了后缀排序问题。

    复杂度 (mathcal{O(n log n)})

    height 数组

    定义

    我们定义 (height_i = mathrm{LCP}(sa_{i - 1}, sa_i)),特别地:(height_1 = 0)

    求解

    我们给出如下事实:

    • (height_{rk_i} ge height_{rk_{i - 1}} - 1)

    反证法。
    若不成立则 (sa_{rk_{i - 1} - 1})(sa_{rk_i - 1}) 更靠近 (sa_{rk_i}),注意一些细节即可。

    由此,我们直接按照 (1 sim n) 的顺序暴力依次求出 (height_{rk_i}),每次的初始答案置为下界即可。

    容易得知这样的复杂度为 (mathcal{O(n)})

    应用

    两个后缀的最长公共前缀

    同样有如下事实:

    • (mathrm{LCP}(sa_i, sa_j) = minlimits_{k = i + 1} ^ j height_k)

    我们考虑将所有的后缀插入到一颗 ( t trie) 树当中。
    那么两个后缀的 ( m LCP) 即为其在 ( t trie) 树上的终止节点的 ( m LCA) 的深度。
    考虑确定 ( m LCA) 的点对为 (x) 的方法,实际上后缀排序就相当于在 ( t trie) 树上的 (dfs) 序,两者就建立起了练习。

    由此,我们可以使用 ( m ST) 表优化到 (mathcal{O}(n log n)) 预处理,(mathcal{O(1)}) 查询。

    比较一个字符串的两个子串的大小关系

    假设两个子串分别为 ([l_1, r_1], [l_2, r_2])

    首先求后缀 (S_{l_1, n}, S_{l_2, n})(mathrm{LCP} = x),若 (x) 超过了两者串长的较小值,那么按照长度为关键字比较,否则比较 (x) 的下一位即可。

    不同子串的数目

    考虑容斥,计算重复出现的子串数目之和。

    我们按照子串出现的时间顺序定义子串是否重复,更进一步地,我们将后缀排序后的第 (i) 个后缀的前缀出现时间设为 (i)

    那么第 (i) 个后缀重复出现的前缀个数为 (height_i),因此重复的子串数目之和为:

    [sumlimits_{i = 1} ^ n height_i ]

    出现至少 k 次的子串的最大长度

    例题:「USACO06DEC」Milk Patterns

    假设字符串 (T)(S) 中出现了,那么出现的位置必定为若干个后缀的前缀。

    因此我们可以考虑 (T)(S) 的所有后缀当中是否出现,进一步地,我们发现其一定出现在后缀数组的一个区间。

    又我们只关心答案的最大长度,因此不在乎具体为哪个子串。

    那么如果存在一个长度为 (x) 的子串出现了 (k) 次,那么后缀数组上一定存在一个长度为 (k) 的区间使得区间内任意两个后缀的 ( m LCP) 长度均不小于 (x)

    换句话说,即存在一个长度为 (k - 1) 的区间使得该区间内的 (height) 数组的最小值 (ge x)

    又如果一个长为 (x) 子串出现了至少 (k) 次,那么一定存在长为 (1 sim x - 1) 的子串出现了至少 (k) 次。

    因此原问题转化为求 (height) 数组中所有长度为 (k - 1) 的区间的最小值的最大值。

    不难发现这就是滑动窗口,可以直接做了。

    询问最长的子串使得其在文本串中至少不重叠地出现了两次

    二分答案 (mid)

    我们枚举其在第二次出现的开头位置 (x),相当于询问是否存在一个位置 (y) 满足 (y le x - mid) 使得 (S_{x, n}, S_{y, n})( m LCP) 长度不小于 (mid)

    我们先限制与 (x, m LCP) 长不小于 (mid) 的字符串在后缀数组上对应的区间 ([l, r])

    问题转化为询问 ([l, r]) 内是否存在一个点使得其下标不大于 (x - mid),不难发现直接维护区间最小值即可。

    使用 ( m ST) 表可做到 (mathcal{O(n log n)})

    结合并查集

    这类问题通常要求在两个后缀的 ( m LCP) 大于某个值的情况下求若干信息。

    因此可以按照 (height) 数组进行排序,将不小于某个值的 (height) 连接的相邻两个节点连边,那么此时构成的连通块内任意两个后缀之间的 ( m LCP) 均不小于 (x)

    结合线段树

    具体问题具体分析。

    结合单调栈

    类似「结合并查集」,通过单调栈求出每个 (height) 管辖的最小值区间,从而将所有后缀点对按照 ( m LCP) 长度划分成 (n) 个区间,这样就可以来求解题目要求的信息了。

    GO!
  • 相关阅读:
    学习FastDfs(三)
    学习FastDfs(二)
    学习FastDfs(一)
    学习ELK日志平台(五)
    学习ELK日志平台(四)
    学习ELK日志平台(二)
    学习ELK日志平台(一)
    并不对劲的CTS2019
    并不对劲的BJOI2019
    并不对劲的bzoj1095:p2056:[ZJOI2007]捉迷藏
  • 原文地址:https://www.cnblogs.com/Go7338395/p/14880574.html
Copyright © 2011-2022 走看看