后缀自动机 SAM
0.前言
这种东西不知道是怎么发明出来了的。感觉很 nb。
但是应该比 KMP 简单些吧。
1.概念
SAM,即后缀自动机,是对于一个字符串 来说能够表示其所有子串/后缀的 DFA。实际上 SAM 是一个 DAG,上面有若干点代表若干状态,其中有一个初始状态 ,这些状态所代表的字符串可能不止一个,SAM 上的边代表若干转移,每个转移是一个字母,从一个状态连接到另一个状态。SAM 上的每条从 作为起始点的路径上的所有转移所组成的字符串实际上是原字符串 的某个子串,这个子串属于这条路径的终点的状态所代表的字符串集合。
2.神秘性质
right 集合
首先我们定义:对于字符串 的一个非空子串 ,其 right 集合为 在 中出现的所有结束位置。
显然,可以得到如下性质:
-
SAM 上的一个状态对应着若干 right 集合相同的子串,如果将这些子串按长度从小到大排序之后,它们的长度连续。
-
-
如果子串 与 的 right 集合相同,那么较短的那个一定是较长的后缀。
后缀链接 fa
根据 right 集合的性质,我们可以根据 SAM 的 DAG 构建 Parent Tree。
对于状态 ,其后缀链接就是连接到 right 集合不同的最长后缀所属状态 上。
令 为状态 中最长的字符串,现在我们知道字符串 的前若干个后缀的 right 集合与 相同,而后若干个后缀的 right 集合不与 相同,令满足后者的最长后缀为 ,则 。
这就相当于不断地在 的开头删除字母,所得到的字符串长度会越来越短,当删到某一个后缀 时,发现其不仅在 出现的这些地方出现过,还在原串 的其他地方出现过。所以其 right 集合会变大,且包含 中的元素。
那么 的后缀链接会连接到 所在状态 上。
-
所有的后缀链接会构成一棵以 为根节点的树。
-
通过 right 集合构造的树(每个子节点的 right 集合都包含在父节点的 right 集合中)与通过后缀链接构造的树相同。
-
在维护 SAM 的时候,通常会记录状态所代表子串的最大长度 ,可以发现,在 Parent Tree 上,一个状态代表子串的长度范围应为 。
上述性质是显然的。我们建出来的自动机实际上是 DAG + Parent Tree。
3.构造 SAM
令一个状态 代表子串中最长子串为 。
当前字符串为 ,新加入的字符为 ,新添加的状态为 。构造过程如下:
从上一次 的状态 开始向上跳后缀链接,直到 在 DAG 上有一条为 的转移;
如果没有找到,那么我们添加的就是一个新字母, 即可。如果找到了,令这个转移到的状态为 :
- 如果 ,证明 只代表 这一个子串,直接将 的后缀链接到 ;
- 如果 ,证明 不止代表 这一个子串,这时我们需要将 拆开,拆为一个只代表 这一个子串的状态 和剩余部分 ,将 的后缀链接连接到 ,并将 与 的后缀链接连接到 ,接着,我们继续用 通过后缀链接向上跳,如果存在状态有为 的转移是指向原本的 的,将其指向 。
这么做是因为我们从 所属状态开始跳后缀链接,而达到的点均为 的后缀,当这些后缀中存在为 的转移时,这个转移所到达的状态一定是 的一个后缀且是最长的 right 集合与 不同的后缀。
SAM 便构造完毕。
4.不错的应用
oi-wiki 已经足够详细。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律