Note - SAM 和广义 SAM

这是萨姆吗?

鉴定为 Hootime 星穹铁道玩多了。


后缀自动机(SAM)是一种能接受一个字符串 \(s\) 的所有后缀的 DFA。

其实如果你想理解 SAM 不太需要理解这句话。

与其它文章不同的是,本文会先讲解后缀链接树的应用,然后再讲解其构造。

从后缀链接树讲起

后缀链接树其实就是反串的后缀树,国内也有人叫它 parent 树。以下可能会混用这两种表述。

接下来我们引入一个概念:


\(\bold{\mathrm{endpos}}\) 集合:我们考虑一个子串,该子串的 \(\mathrm{endpos}\) 集合由其出现的所有右端点的位置组成。

这么说可能不太直观,我们举个例子:有字符串 \(s = \text{ababaa}\)。那么有:

  • \(\mathrm{endpos}(\text{a}) = \{1, 3, 5, 6\}\)
  • \(\mathrm{endpos}(\text{b}) = \{2, 4\}\)
  • \(\mathrm{endpos}(\text{ab}) = \{2, 4\}\)
  • \(\mathrm{endpos}(\text{aba}) = \{3, 5\}\)
  • \(\mathrm{endpos}(\text{abab}) = \{4\}\)
  • \(\mathrm{endpos}(\text{ababaa}) = \{6\}\)

特别地,我们有:

  • \(\mathrm{endpos}(\empty) = \{0, 1, 2, 3, 5, 6\}\)

于是我们有下面这些结论:

对于 \(s\) 的两个不同子串 \(a, b (|a| < |b|)\),若 \(\mathrm{endpos}(a) = \mathrm{endpos}(b)\),那么 \(a\)\(b\) 的后缀,且在 \(s\) 中都是以 \(b\) 的后缀的形式出现的。

正确性挺显然的。如果想不清楚为什么就看看上面 \(\text{b}\)\(\text{ab}\) 的关系。

对于 \(s\) 的两个不同子串 \(a, b(|a| < |b|)\),如果 \(a\)\(b\) 的后缀那么 \(\mathrm{endpos}(b) \sube \mathrm{endpos}(a)\),否则 \(\mathrm{endpos}(a) \cap \mathrm{endpos}(b) = \empty\)

如果 \(\mathrm{endpos}(a) \cap \mathrm{endpos}(b) \neq \empty\),那么 \(a\)\(b\) 在同一个位置结束,所以 \(a\)\(b\) 的后缀,所以每次 \(b\) 出现的时候 \(a\) 也会出现。

\(\mathrm{endpos}\) 相同的所有子串(下称 \(\mathrm{endpos}\) 等价类)中,设最长者为 \(a\),则等价类中所有子串都为 \(a\) 的后缀,且长度取遍某整数区间。

由第一个结论,前半句成立。对于后半句,由第二个结论,我们发现在不断删去 \(a\) 的第一个字符的过程中,\(\mathrm{endpos}\) 要么不变要么增大。


有了 \(\mathrm{endpos}\) 集合我们便可以考虑建出 parent 树。

parent 树:将一个 \(\mathrm{endpos}\) 等价类视为一个节点,parent 树即为某个集合到极小且包含它的集合的树。

比如,根据上面的例子,有以下的树:

然后我们还可以在 parent 树上存储该节点对应的最大长度 \(\mathrm{len}\)。其实我们不需要存最小长度,因为最小长度就是父节点的最大长度 +1(想一想,为什么)。

我们发现我们不能显式地把 \(\mathrm{endpos}\) 存在 parent 树里,事实上要算这个东西也只能用线段树合并和启发式合并。所幸求 \(\mathrm{endpos}\) 的大小是容易的。我们把代表一个后缀的节点点权设为 \(1\)(因为可以添加一个 \(\mathrm{endpos}\)),否则设为 \(0\),求子树和即可。


然后我们看几道题。其实很多题可以抛开自动机直接用 parent 树做,所以自动机成为了快速建立 parent 树的工具,就很尴尬。以下我们默认有一种线性构造 parent 树的方式以及在末尾插入一个字符的方式。

P3804 【模板】后缀自动机(SAM)

给定一个只包含小写字母的字符串 \(S\)

请你求出 \(S\) 的所有出现次数不为 \(1\) 的子串的出现次数乘上该子串长度的最大值。

\(1 \le \lvert S \rvert \le {10}^6\)

这不是板子吗。欸这就是板子来着。

发现一个子串的出现次数等于 \(\mathrm{endpos}\) 的大小,而子串长度取在 \([\mathrm{len}_{\mathrm{fa}_i}+1, \mathrm{len}_i]\) 之间,所以我们对于同一个 \(\mathrm{endpos}\) 等价类只需要管最长的一个就行了。于是算一下就能算出答案。

P4070 [SDOI2016] 生成魔咒

给定序列 \(a\),求 \(a\) 的所有前缀的本质不同连续子序列的数量。

\(1 \le n \le 10^5\)\(1 \leq a_i \leq 10^9\)

发现因为 \(\mathrm{endpos}\) 等价类中的子串长度连续,所以在一个 \(\mathrm{endpos}\) 等价类中子串的个数为 \(\mathrm{len}_{\mathrm{fa}_i} - \mathrm{len}_i\)。我们维护 parent 树,动态加点的同时维护 \(\sum \mathrm{len}_{\mathrm{fa}_i} - \mathrm{len}_i\) 即可。

SAM

好了现在我们只剩下了一个问题:到底怎么建 parent 树 / SAM?

算了到时候再写吧。

posted @ 2026-03-13 09:11  Hootime  阅读(1)  评论(0)    收藏  举报