首页 > 其他分享 >基础后缀数据结构简记

基础后缀数据结构简记

时间:2023-12-03 20:26:17浏览次数:28  
标签:子串 后缀 int 简记 link endpos maxl 数据结构

\[\newcommand{\lcp}{\operatorname{lcp}}\newcommand{\endpos}{\operatorname{endpos}}\newcommand{\link}{\operatorname{link}}\newcommand{\maxl}{\operatorname{maxl}}\newcommand{\minl}{\operatorname{minl}} \]

约定 \(n\) 是字符串长度 . \(\lcp(s,t)\) 是 \(s,t\) 的 LCP 的长度 .

目录

后缀数组

后缀排序

问题:将字符串 \(S\) 的所有后缀排序 .

核心思想:倍增考虑,把从每个位置子串拆成两个减半长度的子串的双关键字排序 .

使用基数排序即可 \(\Theta(n\log n)\),这里 \(n\) 和字符集同阶 .

令 \(sa_i\) 是第 \(i\) 小的后缀的开头位置,\(rk_i\) 是 \(S[i:n]\) 的排名 .

Code

有一些常数优化的技巧可以看 OI-Wiki .

const int m = 1000000;
auto comp = [&](int i, int j, int k){return (y[i] == y[j]) && (y[i+k] == y[j+k]);};
for (int i=1; i<=n; i++) ++buc[x[i] = a[i]];
for (int i=1; i<=m; i++) buc[i] += buc[i-1];
for (int i=n; i>=1; i--) sa[buc[x[i]]--] = i;
for (int k=1; k<=n; k<<=1)
{
	int p = 0;
	for (int i=n-k+1; i<=n; i++) y[++p] = i;
	for (int i=1; i<=n; i++)
		if (sa[i] > k) y[++p] = sa[i] - k;
	memset(buc, 0, sizeof buc);
	for (int i=1; i<=n; i++) ++buc[x[y[i]]];
	for (int i=1; i<=m; i++) buc[i] += buc[i-1];
	for (int i=n; i>=1; i--) sa[buc[x[y[i]]]--] = y[i];
	swap(x, y);
	x[sa[1]] = p = 1;
	for (int i=2; i<=n; i++) x[sa[i]] = comp(sa[i], sa[i-1], k) ? p : ++p;
	if (p >= n) break;
	m = p;
}
for (int i=1; i<=n; i++) rk[sa[i]] = i;

height 数组

问题:求解 \(h_i=\lcp(sa_i,sa_{i-1})\) .

核心思想:\(h_{rk_i}\ge h_{rk_{i-1}}-1\),从而暴力做就是 \(\Theta(n)\) 的 .

Code
for (int i=1, j=0; i<=n; i++)
{
	if (j) --j;
	while (a[i+j] == a[sa[rk[i]-1]+j]) ++j;
	h[rk[i]] = j;
}

常见应用

LCP:变成区间 height 数组的最小值 .

比大小:求出 LCP 后就好处理了 .

本质不同子串:考虑计数重复子串,仔细考察每次加一个子串肯定新增恰 \(h_i\) 个子串,那么就做好了 .

我也没做啥题,就写这三个吧 .

后缀自动机

结构

我们期望得到一个 DFA,恰接受某个字符串 \(S\) 的每个子串 .

对于一个子串 \(T\subseteq S\),定义 \(\endpos(T)\) 为 \(S\) 中所有 \(T\) 的结束位置所构成的集合 .

字符串的所有子串可以由 endpos 分为若干个等价类,下面称 \(u,v\) 等价当且仅当 \(\endpos(u)=\endpos(v)\) .

考虑由此构建一个自动机,其每个状态对应一种 endpos,这样的自动机就被称为 \(S\) 的后缀自动机,转移就是表示每次加一个字符的影响,这里转移构成的 DAG 也被称作 DAWG . 最小性可以由 Myhill–Nerode 定理导出 .

断言:

  1. 满足 \(|u|\le|v|\) 的子串 \(u,v\) 等价当且仅当 \(u\) 在 \(v\) 中的所有出现都作为 \(v\) 的后缀 .
  2. 对于子串 \(u,v\),当 \(u\) 是 \(v\) 的后缀时 \(\endpos(u)\subseteq\endpos(v)\) 否则 \(\endpos(u)\cap\endpos(v)=\varnothing\) .
  3. 对于一个等价类其中包含的子串长度肯定是连续的 .

其实仔细想想都是显然的,这里就不详细说明了 .

关于第三个性质,后令 \(\maxl(u),\minl(u)\) 分别表示一种 endpos \(u\) 对应的最长子串长度和最短子串长度 .

最好类似 AC 自动机,得到一个 fail 树状物,对于等价类 \(u\),考察其所有后缀,记 \(t\) 为和 \(u\) 不在一个等价类里的的最长后缀,\(v\) 是对应等价类,则连边 \((v,u)\) . 由此定义后缀链接 \(\link(u)=v\) .

令根 \(t_0\) 满足 \(\endpos(t_0)=\{-1,0,\cdots,|S|-1\}\),则断言 \(\link\) 关系组成一棵树(通常称为 parent 树).

这是相对显然的,如果你注意到 \(\minl(u)=\maxl(\link(u))+1\) 那么问题就不难了 .

parent 树所得到的树也可以表达 endpos 的包含关系,首先 endpos 的包含肯定组成树,那么只需要说明他们等价即可,问题也就是说明 \(\text{endpos}(v) \subsetneq \text{endpos}(\text{link}(v))\),根据前面的结论这是显然的 .

从而,后缀自动机的基本结构已经被描摹完成 .

构建

这里采用增量构建,也就是说考虑每次加一个字符 \(c\) 产生的影响(这也表明这个算法是在线的).

假设加入前表示整个字符串的结点是 \(lst\),加入后是 \(now\),核心问题就是确定 \(\link(now)\) . 不断跳 \(lst\) 的 link 指针直到跳没了或者其存在一条走 \(c\) 的转移边 . 记最终得到的点是 \(p\),转到 \(q\),考察 \(p\) 这个位置的信息 .

如果跳没了那么肯定是连 \(\link(now)=t_0\) 就完了,否则讨论:

  • \(\maxl(p)+1=\maxl(q)\),这时候相当于直接继承原来等价类,连 \(\link(now)=p\) 即可 .
  • \(\maxl(p)+1\neq\maxl(q)\),这时加入 \(c\) 会导致原等价类分裂,所以需要把 \(p\) 拆成两份,一份作为原来的等价类,另一份作为 \(\link(now)\) . 此时需要把指向 \(q\) 的结点全部改成指向 \(now\) 的 .

仔细地进行分析即可得到加入 \(n\) 个字符的时间复杂度为 \(\Theta(n)\)(如果字符集较大需要使用 Hash).
(具体看 OI-Wiki 吧)

详细算法流程:

Code

注意开二倍空间 .

// len : maxl
struct SAM
{
	int t[N][S], link[N], len[N], lst, cc;
	SAM() : lst(1), cc(1){}
	inline void extend(int c)
	{
		int p = lst, now = lst = ++cc;
		len[now] = len[p] + 1;
		while (p && !t[p][c]){t[p][c] = now; p = link[p];}
		if (!p){link[now] = 1; return ;}
		int q = t[p][c];
		if (len[q] == len[p] + 1){link[now] = q; return ;}
		int k = ++cc;
		memcpy(t[k], t[q], sizeof t[q]);
		len[k] = len[p] + 1; link[k] = link[q]; link[now] = link[q] = k;
		while (p && (t[p][c] == q)){t[p][c] = k; p = link[p];}
	}
};

应用

求子串出现次数:也就是要求 endpos 集合的大小,子串也就是每个前缀的所有后缀,也就相当于把每个前缀对应的结点在 parent 树的根链上每个点贡献都加一,DFS 一遍即可 .

求本质不同子串数:每个等价类 \(u\) 对应的子串个数就是 \(\maxl(u)-\maxl(\link(u))\),全部加起来即可 .

求最长公共子串:考虑对一个字符串建 SAM,另一个字符串在上面跳,每次能转移就转移否则跳 link,对于所有位置取 max 即可 .

后缀 LCP:对于 \(u,v\),\(\maxl(\operatorname{lca}(u,v))\) 就是 \(u,v\) 对应前缀的最长公共后缀(其实算后缀树的结论).

作为练习可以做一下弦论那题,很简单的(突然想起来 DAG 第 \(k\) 大路径是 CSP 初赛题).

广义后缀自动机

相当于对多个字符串的所有子串建立的后缀自动机 .

同样考虑每次加一个字符 \(c\) . 对于每个串分别处理一个 \(lst\) 依次加入,只有一种情况是额外的,也就是 \(lst\) 途径 \(c\) 的转移边存在的情况,设其到达点 \(t\) .

讨论是类似的:

  • \(\maxl(t)=\maxl(lst)+1\),说明根本不用加,最后让 \(lst\gets t\) 即可 .
  • \(\maxl(t)\neq\maxl(lst)+1\),类似地分裂 \(t\) 即可 . 这里新的 \(lst\) 应该是分裂出去的结点 .

那么就完了 . 代码基本是一样的我就不放了 .

后缀树

后缀树的东西我也不是很懂,摆了(

后缀树

问题:求出字符串 \(S\) 所有后缀组成的(压缩)Trie .

这里压缩指压缩一条没有向外的边的链,因为只有 \(O(n)\) 个叶子所以这里的结点数是 \(O(n)\) 的 .

构建

其实也就是说要求所有叶子的虚树,考虑那个单调栈维护右链的构建方法,通过 SA 就天然维护了 .

或者也可以通过 SAM 构建,因为反串的 parent 树就是后缀树 .

标签:子串,后缀,int,简记,link,endpos,maxl,数据结构
From: https://www.cnblogs.com/CDOI-24374/p/17865778.html

相关文章

  • 数据结构 玩转数据结构 14-3 java中的hashCode方法
    0课程地址https://coding.imooc.com/lesson/207.html#mid=15346 1重点关注1.1重写hashCode和equals方法参见3.1  2课程内容2.1不同的对象的默认hashCode方法Integer相同数字的一样Double相同数字的一样String......
  • java后缀名file
    Java后缀名文件Java是一种高级编程语言,经常用于开发各种应用程序。在Java编程中,我们经常会遇到以.java为后缀名的文件。这篇文章将为您介绍Java后缀名文件的相关知识,并提供代码示例来帮助您更好地理解。Java后缀名文件的含义在Java中,后缀名为.java的文件是Java源代码文件的标识......
  • 刷题 后缀和 贪心
    2023.12.2cf1903C 解题思路(听说是个常见的形式)  a:第一个部分b:第二个部分c:第三个部分 本题1*a+2*b+3*c......这种形式可以拆成 a+b+c+...... 加上 b+c+...... 加上 c+...... 由以上看出可以构造后缀和 再证明贪心: 当a*n+b*(n+1)>(a+b)*n......
  • 【数据结构】第一章——习题演练
    导言本篇章题目出自:王道考研系列丛书——《2024年数据结构考研复习指导》课后习题。题目主要考察的是对时间复杂度的分析,在前面的篇章中我们知道时间复杂度是与问题规模n和输入的值k有关的,但是我们在分析时间复杂度时都是以最坏时间复杂度进行分析,这样能确保算法的运行时间不会比......
  • 51k+ Star!动画图解、一键运行的数据结构与算法教程!
    大家好,我是Java陈序员。我们都知道,《数据结构与算法》——是程序员的必修课。无论是使用什么编程语音,亦或者是前后端开发,都需要修好《数据结构与算法》这门课!在各个互联网大产的面试中,对数据结构和算法的考核乐此不疲。往往《数据结构与算法》学得好的,都能拿到高薪!但是《数......
  • 数据结构与算法之单链表-----黑马程序员(26-35)
    1.链表的概念在计算机科学中,链表是数据元素的线性集合,其每个元素都指向下一个元素,元素储存上并不连续。 创建链表如图所示和相关代码publicclassdanlianbiao{privateNodehead=null;//头部第一个结点privatestaticclassNode{//后面的每个结点intvalue;Nodene......
  • 【数据结构】第一章——绪论(4)
    前言大家好!很高兴又和大家见面啦!!!在上一篇内容中我们重点介绍了时间复杂度,今天我们要介绍的是算法的另一个目标——低存储量需求,也就是算法的空间复杂度。下面我们就来了解一下什么是空间复杂度吧!空间复杂度1.定义算法的空间复杂度为算法所消耗的存储空间,它是问题规模的函数。记为:2.......
  • 汇编-数据结构
      .386.modelflat,stdcalloptioncasemap:none.stack4096includewindows.incExitProcessPROTO,dwExitCode:DWORDSTUDENTstruct;自定义数据结构nameDWORD?IDDWORD?STUDENTends.datastwndclassWNDCLASS<>;末初始化st......
  • 数据结构与算法分析(荣政)953 指定教材
    前言953官方指定教材数据结构与算法分析(荣政)绪论数据元素是数据的基本单位数据项是数据的最小单位数据结构:二元组(D,R),D是数据,R是关系,可考判断题,混淆D和R的含义数据结构包含三部分逻辑结构存储结构在逻辑和存储结构上进行的操作抽象数据类型包含三部分逻辑结构:线性和......
  • Golang-常见数据结构实现原理
    chan 1.chan数据结构 src/runtime/chan.go:hchan定义了channel的数据结构:typehchanstruct{qcountuint//当前队列中剩余元素个数dataqsizuint//环形队列长度,即可以存放的元素个数bufunsafe.Pointer//环形队列指针......