后缀自动机(SAM) 学习笔记-程序员宅基地

技术标签: OI的那些事  字符串  后缀  

后缀自动机(SAM) 学习笔记

很久以前学过SAM,现在又忘了。

学习资料

后缀自动机感性理解

史上最通俗的后缀自动机详解

后缀自动机 (SAM)

SAM

如果我们把一个长度为 n n n 的串 S S S 的所有后缀放入同一个 trie 中,并标记结束位置end,可以得到一个时间、空间均为 O ( n 2 ) \mathcal O(n^2) O(n2) 的(假)后缀树。

它具有以下几个性质:

  1. 从根到任意 end 是一个后缀,从根到任意节点是一个子串。
  2. 本质不同的子串个数就是状态(节点)个数。

但是它的时空太大,不满足我们的需求,所以我们采用它的强压缩版本——SAM。

字符串 的 SAM 是一个接受 字符串的所有后缀的最小 DFA (确定性有限自动机或确定性有限状态自动机)。它是一个DAG。

SAM的构造

结束位置 endpos

对于一个 S S S 的子串 t t t,我们用 e n d p o s ( t ) \mathrm{endpos}(t) endpos(t) 表示 t t t S S S 中的所有 结束位置。

如果 两个子串 t 1 , t 2 t_1,t_2 t1,t2 满足 e n d p o s ( t 1 ) = e n d p o s ( t 2 ) \mathrm{endpos}(t_1)=\mathrm{endpos}(t_2) endpos(t1)=endpos(t2),则称它们为 等价类

除初始状态外,每一个等价类对应SAM上的一个状态(节点)。当然可以把初始状态看成空串。

endpos 有以下性质:

  1. 如果 t 1 , t 2 ( ∣ t 1 ∣ ≤ ∣ t 2 ∣ ) t_1,t_2(|t_1|\le|t_2|) t1,t2(t1t2) 的 endpos 相同,那么 t 1 t_1 t1 t 2 t_2 t2 的后缀,且不可能以其它形式出现。
  2. 对于任意两个子串 t 1 , t 2 ( ∣ t 1 ∣ ≤ ∣ t 2 ∣ ) t_1,t_2(|t_1|\le |t2|) t1,t2(t1t2)
    1. t 1 t_1 t1 t 2 t_2 t2 的后缀,则 e n d p o s ( t 2 ) ⊆ e n d p o s ( t 1 ) \mathrm{endpos}(t_2)\subseteq\mathrm{endpos}(t_1) endpos(t2)endpos(t1)
    2. 否则 e n d p o s ( t 1 ) ∩ e n d p o s ( t 2 ) = ∅ \mathrm{endpos}(t_1)\cap \mathrm{endpos}(t_2)=\emptyset endpos(t1)endpos(t2)=
  3. 在一个等价类中存的是一系列 长度连续 的串,并且它们 互为后缀。形式化地,假设这个等价类中的最长串为 S [ x … y ] S[x\dots y] S[xy],则其所有串为 S [ i … y ] ( x ≤ i ≤ z ) S[i\dots y](x\le i\le z) S[iy](xiz)。最长串长度为 m a x l e n = y − x + 1 \mathrm{maxlen}=y-x+1 maxlen=yx+1,最短串长度为 m i n l e n = y − z + 1 \mathrm{minlen}=y-z+1 minlen=yz+1

后缀 link

u u u 为一个非初始状态的状态。根据性质3,它对应一个等价类,这个等价类中的串都形如 S [ i … y ] ( x ≤ i ≤ z ) S[i\dots y](x\le i\le z) S[iy](xiz)。这相当于将长串 S [ x … y ] S[x\dots y] S[xy] 一个个“削去”首字母得到的串。而这些串有什么特性呢?根据性质1,它们之间互相 只以 后缀形式存在。

但是,在削到最短串 S [ z … y ] S[z\dots y] S[zy] 后,如果我继续削,得到的串的 e n d p o s \mathrm{endpos} endpos 就不同了(因为不属于一个等价类了)。根据性质2,它的 e n d p o s \mathrm{endpos} endpos 应该“扩增了”。这个新得到的串 对应的 e n d p o s \mathrm{endpos} endpos 对应的状态 v v v 叫作 u u u 的“父亲状态” (其实就是parent tree 上的父亲)。我们把 后缀 l i n k ( u ) \mathrm{link}(u) link(u) 定义为 v v v,即 u u u 的“父亲”。

于是我们发现,所有的后缀链接会构成一棵树。这个树叫 parent tree。

parent tree除了按后缀链接(自底向上)构造外,还可以理解为 endpos(自上而下)的分裂。

具体实现

以下不再区分 节点、状态 和 等价类,因为它们是一一对应的。

struct Node {
    
	int ch[26], fa, len;
}t[MAXN << 1];
int lst = 1, tot = 1;
void add_sam(int c) {
    
	int p = lst, np = lst = ++tot;
	t[np].len = t[p].len + 1;
	for(; p && !t[p].ch[c]; p = t[p].fa) t[p].ch[c] = np;
	if(!p) t[np].fa = 1;
	else {
    
		int q = t[p].ch[c];
		if(t[q].len == t[p].len + 1) t[np].fa = q;
		else {
    
			int nq = ++tot; t[nq] = t[q];
			t[nq].len = t[p].len + 1;
			t[q].fa = t[np].fa = nq;
			for(; p && t[p].ch[c] == q; p = t[p].fa) t[p].ch[c] = nq;
		}
	}
}

随意说两点吧,可能对理解有所帮助。

一、跳后缀link的意义:跳后缀link即为“压缩地”遍历当前串的全部后缀。如果从节点 p 开始跳,不妨设 p 对应的 等价类中最长串为 S [ x … y ] S[x\dots y] S[xy],那么跳后缀link即为 跳所有 S [ i … y ] ( x ≤ i ≤ y ) S[i\dots y](x\le i\le y) S[iy](xiy) 对应的节点。

二、为什么要遍历 旧串(即加入 c c c 之前的串)的所有后缀:加入 c c c 后我们考虑其对所有节点的endpos的影响。会影响的只会是旧串的后缀,它们都可以+c成为新串的一个后缀。

三、for(; p && !t[p].ch[c]; p = t[p].fa) t[p].ch[c] = np; 这句话的意义:在跳后缀link遍历 ((旧串)的所有后缀)时,如果没有转移边,说明这个等价类中的后缀连上一个c都不曾出现在旧串中出现过。那么现在多接了一个c,可以到达一个新的状态 np(因为这些串肯定是旧串的后缀,加一个c就是新串的后缀)。

四、if(t[q].len == t[p].len + 1) t[np].fa = q; 这句话的意义:说明 q 中的最长串是(p中的最长串+c),所以 q中的所有串都是新串的后缀,它们的endpos没有发生变化,都加入了一个 n n n。所以 q 仍然保持是一个节点。np 的后缀 link 也是 q,这是因为 q 是“(最长的)(不属于np等价类的)(新串的)后缀”。

五、这一段代码:非常重要

	int nq = ++tot; t[nq] = t[q];
    t[nq].len = t[p].len + 1;
	t[q].fa = t[np].fa = nq;
	for(; p && t[p].ch[c] == q; p = t[p].fa) t[p].ch[c] = nq;

由于不满足t[q].len == t[p].len + 1,说明 q 中分为两类串 t 1 t_1 t1 t 2 t_2 t2

  • t 1 t_1 t1 满足 ∣ t 1 ∣ = m a x l e n ( p ) + 1 |t_1|=\mathrm{maxlen}(p)+1 t1=maxlen(p)+1,同 四、 所述,它是新串的后缀,也应同 四、中一般处理。我们把它分裂出去形成一个新的等价类 nq。
  • t 2 t_2 t2 满足 ∣ t 2 ∣ > m a x l e n ( p ) + 1 |t_2|>\mathrm{maxlen}(p)+1 t2>maxlen(p)+1,它 不是 新串的后缀。那么它们的 endpos 就不能加 n n n

然后调整后缀link和转移即可。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_41996523/article/details/113757039

智能推荐

mysql dwith boost_linux下Mysql 8.0.19 编译安装-程序员宅基地

文章浏览阅读951次。1 前言linux下安装MySQL的方式有很多种,包括以仓库的方式安装(yum,apt,zypper),以包的方式安装(rpm,deb),以docker方式安装,从压缩包解压安装,从源码编译安装,这里使用的是最后一种,从源码编译安装。编译安装需要大量的耐心与时间,而且还会遇到非常多奇奇怪怪的问题,因此,需要极大的毅力,很有可能一万次失败也换不来一次的成功,请做好心理准备。2 准备工作下面是安装要求..._mysql dwith boost

mysql 高级(进阶学习)_mysql高级进阶-程序员宅基地

文章浏览阅读1.4k次,点赞2次,收藏8次。视图就是将某个查询语句存储在数据中,并为其命名,视图中并不存储数据,数据还是在基本表中存储。定义视图使用视图删除视图存储过程就是把一段处理逻辑存入到数据库中,使用是就由 JDBC 调用即可。调用存储过程可以减少应用程序和数据库交互次数,在数据库内部执行,执行效率高。存储事先需要定义,有三种参数类型:in 入参(接收调用者传入的数据)out 返回(向调用者返回数据)inout (既可以接收调用者传入的数据,也可以向调用者返回数据)函数是一个特殊的存储过程。存储过程不仅有输入参数,还有输出参数,但是没有返回值,_mysql高级进阶

goquery php,golang:Goquery简单爬虫实例-程序员宅基地

文章浏览阅读189次。Selection类型提供的方法,这些方法是页面解析最重要,最核心的方法1)类似函数的位置操作-Eq(indexint)*Selection//根据索引获取某个节点集-First()*Selection//获取第一个子节点集-Last()*Selection//获取最后一个子节点集-Next()*Selection..._goquery获取tbody的数据

计算机资源库在哪,电脑的资源管理在哪里-程序员宅基地

文章浏览阅读2.7k次。语音内容:大家好,我是时间财富网智能客服时间君,上述问题将由我为大家进行解答。电脑的资源管理的位置:1、单击开始菜单,在弹出的快捷菜单中选择文件资源管理器。2、按组合键Win+R打开运行窗口。3、在运行窗口中输入命令:explorer按回车键执行命令即可以打开资源管理器窗口。4、在桌面的任务栏上右击鼠标,在弹出的快捷菜单中选择“任务管理器。5、在任务管理器的菜单栏中选择文件中运行新任务。6、在运行..._计算机库到哪里找

适合新手的CTF靶场合集-程序员宅基地

文章浏览阅读3.4w次,点赞42次,收藏344次。前言我整理得没有那么全,这里的合集主要还是面对新手。做题贵精不在多,好好练习每一题,学习每个知识点,不懂的百度或者 Google 即可。记住,你是为了提高自己而去打 CTF 。CTF 比赛时间表CTFwiki(入门必看wiki): https://ctf-wiki.github.io/ctf-wiki/#/introduction XCTF社区: https://time.xctf...._ctf靶场

彻底理解位运算——左移、右移_左移和右移-程序员宅基地

文章浏览阅读6.2w次,点赞168次,收藏598次。相信大家在各种语言各种框架中都能看到二进制的操作。左移、右移、&、|、^等等操作。那么这篇帖子让各位彻底弄懂左移、右移。首先先区分那个是左移、那个是右移,这很简单,从箭头指向的方向来区分。右移左移:很简单的来说就是把当前的二进制,整体往左边移动N个单位,N取决于你的表达式。那么用一个例子,和画图来理解一下吧。32 ..._左移和右移

随便推点

Docker安装MySQL、nginx并且部署SpringBoot项目前后端(超详细版)_docker desktop 整合 nginx 和spring-程序员宅基地

文章浏览阅读1.4k次,点赞24次,收藏16次。超级详细的Docker部署Springboot项目的步骤,大家只需要按照文档一步一步的复制粘贴即可。_docker desktop 整合 nginx 和spring

Freescale I.mx 6 Android 4.2.2源码编译环境搭建(基于ubuntu12.04 LTS)_note, selecting 'lib32z1-dev' instead of 'lib32z-d-程序员宅基地

文章浏览阅读2.9k次。Freescale I.mx 6 Android 4.2.2源码编译环境搭建 1 安装必要的第三方工具:$ sudo apt-get install git gnupg flex bison gperf build-essential \ zip curl zlib1g-dev libc6-dev lib32ncurses5-dev ia32-libs \ x11proto-_note, selecting 'lib32z1-dev' instead of 'lib32z-dev

大数据存储架构详解:数据仓库、数据集市、数据湖、数据网格、湖仓一体_数据仓库 数据集市 数据湖-程序员宅基地

文章浏览阅读1.3w次,点赞7次,收藏75次。本文以文字+思维导图+表格的形式详解了数据库、数据仓库、数据集市、数据湖、数据网格、湖仓一体之间的区别。_数据仓库 数据集市 数据湖

如何运用python多线程threading实现程序的并发_python 如何实现并发获取数据-程序员宅基地

文章浏览阅读443次。每次技术的进步都是面对问题解决问题,有了现实中需要解决的问题了我们才能想各种方法解决他也就成就了技术的跃迁。_python 如何实现并发获取数据

柴天佑pdf 自适应控制_串讲:控制理论:自适应控制(APC)-程序员宅基地

文章浏览阅读4k次,点赞3次,收藏21次。自适应控制 (APC)说道自适应控制(APC),也要追溯到5年前第一次接触,当时还只会应用下面的自适应律公式来求解,这里结合自己的一些想法来对自适应控制进行深入剖析,希望可以帮助到大家。APC的历史:在早期的二十世纪五十年代,APC被开始研究,当时应用在飞机的自动导航装置上。简而言之,APC是一种带有在线参数识别的控制方法,主要可以被分为模型参考自适应控制(MRAC)、自校正控制器(STC)、参数..._自适应控制 柴天佑pdf

浅谈Overlay File System的应用_filesystem overlay-程序员宅基地

文章浏览阅读6.7k次,点赞3次,收藏18次。Overlay FS在Docker中的使用_filesystem overlay

推荐文章

热门文章

相关标签