后缀自动机 SAM

0.前言

这种东西不知道是怎么发明出来了的。感觉很 nb。

但是应该比 KMP 简单些吧。

oi-wiki

1.概念

SAM,即后缀自动机,是对于一个字符串 s 来说能够表示其所有子串/后缀的 DFA。实际上 SAM 是一个 DAG,上面有若干点代表若干状态,其中有一个初始状态 t0,这些状态所代表的字符串可能不止一个,SAM 上的边代表若干转移,每个转移是一个字母,从一个状态连接到另一个状态。SAM 上的每条从 t0 作为起始点的路径上的所有转移所组成的字符串实际上是原字符串 s 的某个子串,这个子串属于这条路径的终点的状态所代表的字符串集合。

2.神秘性质

right 集合

首先我们定义:对于字符串 s 的一个非空子串 s0,其 right 集合为 s0s 中出现的所有结束位置。

显然,可以得到如下性质:

  1. SAM 上的一个状态对应着若干 right 集合相同的子串,如果将这些子串按长度从小到大排序之后,它们的长度连续。

  2. {right(x)right(y)if y is a suffix of x.right(x)right(y)=ϕotherwise.

  3. 如果子串 xy 的 right 集合相同,那么较短的那个一定是较长的后缀。

后缀链接 fa

根据 right 集合的性质,我们可以根据 SAM 的 DAG 构建 Parent Tree。

对于状态 X,其后缀链接就是连接到 right 集合不同的最长后缀所属状态 Y 上。

t 为状态 X 中最长的字符串,现在我们知道字符串 t 的前若干个后缀的 right 集合与 t 相同,而后若干个后缀的 right 集合不与 t 相同,令满足后者的最长后缀为 v,则 right(t)right(v)
这就相当于不断地在 t 的开头删除字母,所得到的字符串长度会越来越短,当删到某一个后缀 v 时,发现其不仅在 t 出现的这些地方出现过,还在原串 s 的其他地方出现过。所以其 right 集合会变大,且包含 right(t) 中的元素。
那么 X 的后缀链接会连接到 v 所在状态 Y 上。

  • 所有的后缀链接会构成一棵以 t0 为根节点的树。

  • 通过 right 集合构造的树(每个子节点的 right 集合都包含在父节点的 right 集合中)与通过后缀链接构造的树相同。

  • 在维护 SAM 的时候,通常会记录状态所代表子串的最大长度 lenu,可以发现,在 Parent Tree 上,一个状态代表子串的长度范围应为 [lenfau+1,lenu]

上述性质是显然的。我们建出来的自动机实际上是 DAG + Parent Tree。

3.构造 SAM

令一个状态 u 代表子串中最长子串为 longest(u)

当前字符串为 s,新加入的字符为 c,新添加的状态为 u。构造过程如下:

从上一次 s 的状态 p 开始向上跳后缀链接,直到 p 在 DAG 上有一条为 c 的转移;
如果没有找到,那么我们添加的就是一个新字母,fau=t0 即可。如果找到了,令这个转移到的状态为 q

  1. 如果 lenq=lenp+1,证明 q 只代表 longest(p)+c 这一个子串,直接将 u 的后缀链接到 q
  2. 如果 lenq>lenp+1,证明 q 不止代表 longest(p)+c 这一个子串,这时我们需要将 q 拆开,拆为一个只代表 longest(p)+c 这一个子串的状态 now 和剩余部分 q,将 now 的后缀链接连接到 p,并将 qu 的后缀链接连接到 now,接着,我们继续用 p 通过后缀链接向上跳,如果存在状态有为 c 的转移是指向原本的 q 的,将其指向 now

这么做是因为我们从 s 所属状态开始跳后缀链接,而达到的点均为 s 的后缀,当这些后缀中存在为 c 的转移时,这个转移所到达的状态一定是 s+c 的一个后缀且是最长的 right 集合与 right(u) 不同的后缀。

SAM 便构造完毕。

4.不错的应用

oi-wiki 已经足够详细。

posted @   songszh  阅读(8)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示