首页 > 其他分享 >后缀数据结构

后缀数据结构

时间:2024-04-12 21:14:34浏览次数:27  
标签:log 后缀 复杂度 height mathcal 数据结构 rk

by Duck.

后缀数组

参考:后缀数组简介 - OI Wiki

后缀数组(Suffix Array,SA)可以在 \(\mathcal{O}(n \log n)\) 的复杂度内对 \(S\) 的每个后缀进行字典序排序。

记后缀 \(i\) 表示后缀 \(S[i,n]\)。

SA 的核心在于得到两个数组 \(sa,rk\)。\(sa_i\) 表示字典序排名为 \(i\) 的后缀位置,\(rk_i\) 表示后缀 \(i\) 的字典序排名。容易发现这两个数组是互为逆运算的,也就是 \(sa(rk_i)=rk(sa_i)=i\)。

朴素的后缀排序,每次对两个串比较字典序需要 \(\mathcal{O}(n)\) 的复杂度,总复杂度 \(\mathcal{O}(n^2 \log n)\)。

SA 运用了倍增的思想,每次对等长的串进行字典序排序。对于长度不相同的,我们在末尾添加很小的字符保证其字典序更小。对于等长的字符串,我们可以将其切成两半,对于前后两端分别比较字典序。这件事为倍增比较的正确性提供了保证。

一个更重要的东西是 height 数组,绝大多数的 SA 题目都依赖于此。

定义 \(h_i=\operatorname{lcp}(sa_i,sa_{i-1})\),也就是排名相邻的两个后缀的 \(\text{lcp}\)。不妨认为 \(h_1=0\)。

关于 \(h_i\) 最重要的一个性质:\(h(rk_i) \geq h(rk_{i-1})-1\)。这个性质可以帮助我们快速递推 height 数组。

height 数组的一些经典应用:

  • 求后缀 \(i,j\) 的最长公共前缀:\(\operatorname{lcp}(i,j)=\min_{k=rk_i+1}^{rk_j}\{h_k\}\)。简要的解释为,对于排名在 \([rk_i,rk_j]\) 之间的所有后缀,他们的前 \(\operatorname{lcp}(i,j)\) 个字符必然都是相等的,从而答案就是 \([rk_i+1,rk_j]\) 的区间 \(\min\)。
  • 本质不同子串数量:\(\dfrac{n(n+1)}{2}-\sum h_i\)。简要的解释为,重复的子串一定来源于排名相邻的后缀的 LCP。

P3809 【模板】后缀排序

在这里详细说一下 SA 的求解过程。

设 \(^wrk\) 表示长度为 \(w\) 的子串比较得到的 \(rk\) 数组,以 \(^{w/2}rk_i\) 和 \(^{w/2}rk_{i+w/2}\) 作为二元组的第一二关键字排序,就可以得到长度为 \(w\) 的子串的排序结果 \(^wsa\) 和 \(^wrk\)。二元组排序用基数排序即可。

当 \(w\geq n\) 时就可以得到每个后缀的排序结果。实现细节可以直接背板子。

时间复杂度 \(\mathcal{O}(n \log n)\)。

提交记录

P4051 [JSOI2007] 字符加密

我们把原串复制一遍接在后面,那么所有循环同构都会作为一个子串出现。

根据 SA 比较字典序的原则,「把字符串切成前一半和后一半分别比较」是正确的,所以我们对整个串进行后缀排序,比较的依据只有这些循环同构。从而就实现了对所有循环同构字典序排序的效果。

时间复杂度 \(\mathcal{O}(n \log n)\)。

提交记录

P4248 [AHOI2013] 差异

\(|T_i|+|T_j|\) 是定值,所有数对加起来会贡献 \((n-1)\times \dfrac{n(n+1)}{2}\)。只需要算后面的 LCP 部分。

考虑 height 数组,那么这个东西相当于求所有区间的最小值之和。用单调栈算一下作用区间即可。

时间复杂度 \(\mathcal{O}(n \log n)\)。

提交记录

P7409 SvT

与上一题需要处理的问题是一样的,但是这里每次询问会给出一个点集。

考虑用虚树的思想减少状态,将给定的点 \(a_i\) 按照 \(rk\) 排序并离散化之后,求出每个点新的 \(h'_i=\operatorname{lcp}(sa(a_i),sa(a_{i-1}))\),再用单调栈去处理。

先建出 SA 和 height 数组,用 ST 表预处理区间最小值即可每次 \(O(1)\) 回答 \(h'_i\)。

时间复杂度 \(\mathcal{O}(n \log n + \sum t \log t)\)。

提交记录

CF1073G Yet Another LCP Problem

还是一样的问题,这回变成在两个集合里面选了。

于是我们简单容斥一下,分别计算只在 \(A\) 里选,只在 \(B\) 里选,以及在 \(A\cup B\) 里选的答案即可。

时间复杂度 \(\mathcal{O}(n \log n + \sum k\log k+\sum l\log l)\)。

提交记录

P4070 [SDOI2016] 生成魔咒

这个东西要求我们动态维护 height,这当然是不可能的。

如果每次都要改变后缀,自然是困难的,那么我们考虑把串反过来,这样每次相当于插入一个新的后缀,并且不影响之前的所有后缀。

这件事情告诉我们,插入一个新的后缀只会影响与其排名相邻的后缀的 height。于是我们先把所有数离线下来反转建 SA,用 set 维护所有数的 height。插入新后缀,只需要减去其排名的前驱和后继的 LCP,再加上它和前驱以及它和后继的 LCP 即可。

时间复杂度 \(\mathcal{O}(n \log n)\)。

提交记录

P3181 [HAOI2016] 找相同字符

一种神秘套路。

对于两个串的的问题,我们可以考虑将其拼接在一起,中间加一个分隔符,对这个大的串后缀排序。对于多个串,一样可以拼接,要在每两个串之间加上互不相同的分隔符。

对于这个题,我们考虑拼接,算所有相同的子串数量之和,这个东西就是后缀两两 LCP 之和。同时,我们还需要减掉只在 \(A\) 中出现的子串和只在 \(B\) 中出现的子串。于是我们建三个 SA,跑三次 height 数组,单调栈处理即可。

时间复杂度 \(\mathcal{O}(n \log n)\)。

提交记录

SP1811 LCS - Longest Common Substring

考虑拼接在一起后缀排序,最长公共子串一定出现在排名相邻的后缀的 LCP 中。

按照排名从小到大枚举,判断两个后缀是否一个属于 \(A\) 一个属于 \(B\),对 height 数组取最大值即可。

时间复杂度 \(\mathcal{O}(n \log n)\)。

提交记录

SP1812 LCS2 - Longest Common Substring II

多个串的问题。

类似的问题还有:SP10570,P5546。

P3975 [TJOI2015] 弦论

P9482 [NOI2023] 字符串

标签:log,后缀,复杂度,height,mathcal,数据结构,rk
From: https://www.cnblogs.com/duckqwq/p/18132090

相关文章

  • 数据结构(图)
    图是一种非线性数据结构,由顶点(节点)和边组成,用于描述不同对象之间的关系。在图中,顶点表示对象,边表示对象之间的关系,可以是有向的(箭头表示方向)也可以是无向的(没有方向)。图的定义包括以下几个重要概念:顶点(Vertex):图中的节点,可以表示对象或实体。边(Edge):连接顶点的线,表示顶点之间的关......
  • 数据结构(堆)
    堆就是用数组实现的二叉树,所以它没有使用父指针或者子指针。堆根据“堆属性”来排序,“堆属性”决定了树中节点的位置。堆的常用方法:构建优先队列支持堆排序快速找出一个集合中的最小值(或者最大值)在朋友面前装逼堆属性堆分为两种:最大堆和最小堆,两者的差别在于节点的排序方......
  • C#开发用到的数据结构
    引用:https://zhuanlan.zhihu.com/p/193997869数据结构graphLR数据结构-->线性结构-->数组线性结构-->顺序表线性结构-->链表线性结构-->栈线性结构-->队列数据结构-->非线性结构非线性结构---->树形结构树形结构-->二叉树二叉树-->二叉查找树二叉树-->红黑树树形......
  • 几种常用数据结构的C语言实现
    队列/*********************************************************************************@file:myfifo.c*@brief:先入先出队列实现*@author:huanglidi*****************************************************************......
  • 数据结构之顺序表(java语言版)
    顺序表是最简单的线性表,也就是数组。很多语言都把把它当做内置的基本数据类型,这里的数组没有对应数据结构的操作。数组是顺序存储的结构,连续分配一段内存用于存储数据。在逻辑结构和物理结构上都是连续的。顺序表建立在java内置的数组上建立顺序表。publicclassArray{ pri......
  • 数据结构之栈(java语言版)
    栈(stack):在逻辑上是一种线性存储结构,它有以下几个特点:1、栈中数据是按照"后进先出(LIFO,LastInFirstOut)"方式进出栈的。2、向栈中添加/删除数据时,只能从栈顶进行操作。栈通常包括的三种操作:push、peek、pop。push--向栈中添加元素。peek--返回栈顶元素。pop--返......
  • 数据结构之队列(java语言版)
    队列(Queue):在逻辑上是一种线性存储结构。它有以下几个特点:1、队列中数据是按照"先进先出(FIFO,First-In-First-Out)"方式进出队列的。2、队列只允许在"队首"进行删除操作,而在"队尾"进行插入操作。队列通常包括的两种操作:入队列和出队列。队列的种类也很多,单向队列,双向队列,循......
  • 数据结构之二叉树(java语言版)
    之前的都是线性结构,而树结构在计算机应用中的应用更加广泛。linux中的目录结构,某些数据库的底层存储等,都是采用树结构进行构架的。树的概念线性表是一对一的关系,而树是一对多的关系。树的结点:包含一个数据元素及若干指向子树的分支;孩子结点:结点的子树的根称为该结点的孩子;双......
  • 数据结构之图(java语言版)
    图是比树更复杂的结构,树是一对多的关系,图是多对多的关系。一、基本概念1、定义:图(graph)是由一些点(vertex)和这些点之间的连线(edge)所组成的;其中,点通常被成为"顶点(vertex)",而点与点之间的连线则被成为"边或弧"(edege)。通常记为,G=(V,E)。2、根据边是否有方向,将图可以划分......
  • 数据结构之Hash(java语言版)
    Hash表Hash也叫散列、哈希,是一种根据key-value对进行存储的数据结构。每个value对应一个key,这样查找的时候就无需遍历。Hash表使用数组作为底层结构,数组中每个区域都存储着Hash,这就是Hash表。列表、数组、树这些数据结构在查询数据时的时间复杂度通常为O(n),而Hash的时间复杂......