强势图解后缀自动机
题外话:
话说我个人觉得后缀自动机其实也并不难,跟自动机都差不多吧(特别是模板代码短)
如果有任何错误或着有更好的理解,请联系我!
该文好像有问题,建议批判性的观看,主要是我忘得差不多了,不会改了
前置知识:字典树
其实后缀自动机和自动机一样都是字典树,下面以acabb为例子来详解:
如果按照普通的字典树来建造的话,是以下的图:
但是我们看着可以发现有很多很多点都是重复的,浪费了很多空间,而且我们看到每个点大部分只有一个儿子,我们就想到利用公共部分把空间压缩,即把某些重复的边删去,连接到别的子树,从而利用公共部分降低空间复杂度,同时我们还要保证新的做法的正确性,并且降低时间复杂度降到大概,为了解决这个问题,后缀自动机诞生了
下图是后缀自动机成型样子(没显示指针):
后缀自动机是这样的:在后缀自动机中,为了节省空间,某个点有可能成为多个结点的儿子,可以保证在后缀自动机中遍历出来的所有字符串不会重复,而且刚好是原串s的所有子串。
基本储存信息:
、:基本的字典树;
、:指向上一个与当前节点等价的点
、:表示以当前点为终点的子串的长度
后缀自动机的性质:
、从任意节点到任意节点结束的路径都是文本串T的子串。
、后缀自动机是一个根为的有向无环图
、任何一个从任意节点到达任意节点p的路径是从根节点到p节点最长路径的一个后缀
……
算法流程:(假设要插入的字符为)
1、定义一个变量; //为上一次的节点;
2、定义一个变量 //为节点编号(也可以理解为时间戳吧,反正差不多就是一种顺序),为新添加的节点
3、; //更新上次的节点以及新节点的长度
4、循环判断:当点没有到的转移的话,;即把点添加到的转移为,并且点跳指针,当点有到的转移时停止循环
5、当此时点为时,把的指针赋为(因为根节点为)
6、否则的话,意味着此时的点有到的转移,我们定义
7、当时,把赋为并退出即可(具体后面会详细讲)
8、否则的话,我们定义一个新节点,复制节点的数组,指针给,,并且把,的指针赋为。循环跳点的指针,每次当时,赋为
(看到这里时是会有点懵逼的,等会结合下文图解及分析一起看)
先贴这部分代码(方便以后使用):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | int last=1,cnt=1; //初始化 void Insert( int c) //插入,联系上文文字看吧 { int p=last,np=++cnt;last=np;len[np]=len[p]+1; for (;p&&!ch[p][c];p=fail[p]) ch[p][c]=np; if (!p) fail[np]=1; else { int q=ch[p][c]; if (len[q]==len[p]+1) fail[np]=q; else { int nq=++cnt; len[nq]=len[p]+1; memcpy(ch[nq],ch[q], sizeof (ch[q])); fail[nq]=fail[q]; fail[q]=fail[np]=nq; for (;ch[p][c]==q;p=fail[p]) ch[p][c]=nq; } } } |
强势图解开始!!!
首先,假设我们已经建立好了文本串的后缀自动机,现在要在后面插入字符,使自动机成为字符串的后缀自动机,那么我们先建立一个新节点,并且找到上一个节点,循环判断如果没有儿子的话,那么就向连一条的转移,点跳指针,直到点到了虚拟节点或者有向x的转移时,停止循环。如果点此时是因为有向x的转移而退出的循环,即p!=0。假设此时点向x的转移为节点,那么就会有以下两种情况:
1、
我们因为想要压缩空间,那么就必须要共用已有的节点,下面是这种情况的图:
那么我们先考虑从点连一条的转移到,但是这样可行吗?我们可以看出如果这样连的话,点就没有办法到达了,也就是说这一字串将会被破坏,那么怎么办呢?我们考虑把当做是(因为都是向的转移),也就是说,把也当做是的某个字串的结尾,把的指针指向,从而保证算法的正确性(即性质)
说到这可能大家会有疑问,如果我们不走节点就到了节点,那就不能保证到节点的都是的后缀了。其实就已经保证了经过了就必定经过了,如果不经过,那就只能从节点直接来了,为什么呢,我们运用反证法(转自某大佬):
假设原命题不成立,那么就有两种可能:(补充:现在文本串为,也就是说是终点)
一.当前的字符,之前没有出现过.这样的话,有字符的子串必然是后缀,与假设矛盾.
二.当前的字符,之前已经出现过.这样的话,有字符而不是后缀的子串必然与之前的某个代表字符的结点连接,而不是与当前的点连接,否则后缀自动机的性质早就被破坏了,故也与假设矛盾。
那么结束后的图是这样的(红色虚边为指针,实线黑边为数组):
2、
这种情况就是下图:
也面临着点能不能当做点的问题。但是这种情况与第一种情况的区别在于:第一种情况,中间不会夹杂其他的字符,而这种情况,中间是会有其他字符的,我们就不能保证到达节点的一定是的后缀了。
那么我们考虑能不能把这种情况能不能转化为第一种情况呢?答案是肯定的,我们考虑新建一个节点,使得,这样的话我们就转化为了第一种问题,那么我们把节点所有东西复制给新节点的话,就让充当第一种问题中的,那么我们把,的指针赋值为,的指针为,同时还必须记住让节点跳指针,把所有连向节点的边都连向(因为节点代替了节点)
操作完成后就是下图:
图解acabb的后缀自动机过程,建议和代码一起理解!
(没有动图。。。动图太快了不好理解其实是本人不会)
1、插入字符:
因为一开始等于(根节点),而所以根节点没有向的转移,因此向连一条向的转移,然后跳指针,然而,也就是指向了虚拟节点,所以(指向根节点)
2、插入字符:
建立新的节点,节点为上一个节点,没有向的转移,因此添加一个向的转移指向,点跳指针到了,节点也没有向的转移,也添加一个向的转移到,跳指针到(虚拟节点),所以节点的指针指向(根节点)
3、(重点)插入字符
这种情况就是的情况了,首先为节点,然而现在节点没有向的转移,于是向节点连一条的转移
并且点跳指针到节点,如下图:
而这时候我们发现节点有的转移指向号节点, 并且满足上面所述的第一种情况即,所以直接把节点当做节点
把赋值为,即下图:
4、插入字符
节点没有的转移,于是点向连一条的转移
点跳指针:
我们发现点没有的转移,于是点向连一条的转移
点跳指针:
最后点到了虚拟指针,所以将赋值为根节点,完成后如下图:
5、(重点)插入字符:
这种情况就是第二种情况:
首先我们发现节点没有的转移,于是添加一个到的转移为
跳指针到:
我们发现点有的转移到节点,并且满足情况(),复制点的信息到点(因为它要代替节点),即,并且也像一样连一条的转移到,同时把,的指针赋值为
最后我们还要按照下图一样,点跳指针把所有连向的转移都连向(代替了),如下图:
最终的后缀自动机就完成啦!
把指针去掉就是下图:
后缀自动机的应用(借鉴了某大佬):
1、检查字符串是不是文本串的子串
给定一个文本串,求字符串是不是的子串
首先,我们对文本串建立后缀自动机,然后在自动机上直接按照的每个字符来转移,如果转移失败的话,说明不是的子串,这些都是因为后缀自动机满足性质。
2、不同的子串
给你一个文本串,求一共有多少子串
后缀自动机性质,因为后缀自动机是一个有向无环图,所以我们可以考虑在上面简单的,根据性质,任何子串都会是后缀自动机上的一段路径(包括长度为的路径),所以我们令为节点不同路径的条数,即从节点开始有多少不同子串,状态转移方程就是:
那么我们最后的答案的话就是因为要去掉根节点长度为的串
如果要考虑按字典序输出的话,那么就用一个手写栈来写,每次走字典序小的边,走到一个点就输出当前的栈内元素,递归后要回溯!
3、字典序第大的子串
其实这个问题是基于上面这个问题的,我们既然已经求出了每个点不同路径的条数,那么我们就可以选择性的走k小路径
4、字典序最小循环移位
给定一个文本串,每次操作可以把最左边的字符移到最右边,请求出字典序最小的循环移位
这个问题的话其实做多了就知道了,我们以来建立后缀自动机,这样的话后缀自动机就会包含每个循环移位的路径,那么我们直接贪心来找字典序最小就行了
5、求两串中的最长公共子串
给定两个字符串为和,求出它们的最长公共子串
对于这个问题,我们对字符串建立后缀自动机,对于的每个前缀,在自动机里转移状态,定义一个变量,一个变量,分别表示现在匹配的长度,以及现在的位置。我们每匹配成功一次,自增一,直到没有状态转移的时候,我们就跳指针,而此时就要赋值为,直到指向虚拟节点(也就是失配,此时)或匹配完成,而答案就是的最大值
6、出现次数
对于一个给定的文本串 $T$,有多组询问,每组询问给一个模式串 ,回答模式串 在字符串 $T$ 中作为子串出现了多少次
我们为文本串建立后缀自动机,为每个节点定义一个变量,初始化为,根节点与复制节点nq除外,那么我们对每个节点做如下操作:,含义是当节点出现了次,那么以它为后缀的点也会出现这么多次。最后查询,就是模式串的状态,查询不到则为
练习:
没什么好说的,就是模板
要用用上面的知识点,灵活运用吧,相信你会举一反三的
本篇博客就到这里结束了,如果觉得有帮助不要吝啬你们的赞
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】