后缀自动机(SAM)

发布时间 2023-12-30 18:55:07作者: yisiwunian

OI WIKI进行了抄写删减精炼

定义

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

  • SAM是一张有向无环图。节点称作状态,边称作转移

  • 图存在一个源点\(t_0\),称作初始状态(即下图红点),其他节点均可从\(t_0\)出发到达

  • 转移依赖字符进行

  • 存在一个或多个终止状态(下图绿点)

  • 在满足以上的同时节点数最少

可看作对字符串所有后缀建Trie树,然后对相同的状态(即下文endpos集合相同)缩点

以aaba为例

以abbb为例

线性构造

先明确一些概念

结束位置endpos

字符串s的任意非空子串t,记\(endpos(t)\)为t在s中的所有结束位置。如abcbc,\(endpos(bc)\)=3,5。
两个子串endpos集合可能相等:\(endpos(t_1)=endpos(t_2)\)。于是将所有子串按照endpos集合分为若干等价类。

SAM中每个状态对应一个或多个endpos相同的子串。加上初始状态,SAM的状态个数等于endpos相同的一个或多个子串组成的集合个数+1。

lemma1

两个非空子串 u 和 w(假设 ∣u∣≤∣w∣)的 endpos 相同,当且仅当字符串 u 是 w 的后缀。

lemma2

考虑两个非空子串 \(u\)\(w\) (假设 \(\left|u\right|\le \left|w\right|\) )。那么要么 \(\operatorname{endpos}(u)\cap \operatorname{endpos}(w)=\varnothing\) ,要么 \(\operatorname{endpos}(w)\subseteq \operatorname{endpos}(u)\) ,取决于 \(u\) 是否为 \(w\) 的一个后缀:
\(\begin{cases} \operatorname{endpos}(w) \subset \operatorname{endpos}(u) & \text{if } u \text{ is a suffix of } w \\ \operatorname{endpos}(w) \cap \operatorname{endpos}(u) = \varnothing & \text{otherwise} \end{cases}\)

lemma3

将一个endpos等价类中所有子串按长度递增排序,长度形成连续段

对于一个状态p,link指向最近的一个q,\(\operatorname{endpos}(p)\subsetneq \operatorname{endpos}(q)\)

以abcbc为例,link构成一棵树,父亲是儿子的最大后缀

最大长度len

对于一个状态p,其表示的所有子串中最大长度为len,最小长度为minlen

考虑一个子串,其endpos与p有交且长度在minlen到len之间,则这个子串包含在状态p中,印证lemma3

在考虑一个子串,其endpos与p有交且长度小于minlen,则这个子串是p在link上的祖先。于是得到\(minlen[p]=len[link[p]]+1\),所以不需要记录minlen。

在link树上,从根到叶子的一支,其子串的长度是连续的,且上为下的后缀

到现在你可以发现,后缀自动机中的后缀只存于link关系中,而节点存的是子串

过程

一开始只包含一个状态\(t_0\),编号为0,len=0,link=-1

现加入一个字符\(c\)

创建新状态cur,lst为加入上一个的状态,首先就有\(len[cur]=len[lst]+1\),即全串。

然后从lst开始向上跳link,对其添加字符\(c\),即对当前串的所有后缀添加字符\(c\)。若没有\(c\)的转移,就一路添加到\(t_0\),这个字符从没出现过,link=0。

假设现在我们找到了一个状态p,其可以通过字符 \(c\) 转移到状态q,停下。此时p上面的所有状态都有\(c\)的转移。

记向 SAM 中插入当前字符 \(c\) 之前的字符串为 \(s\)

转移 \((p,q)\)意味着我们尝试向自动机内添加一个 已经存在的 字符串 \(x+c\) (其中 \(x\)\(s\) 的一个后缀,且字符串 \(x+c\) 已经作为 \(s\) 的一个子串出现过了)。因为我们假设字符串 [s] 的自动机的构造是正确的,我们不应该在这里添加一个新的转移。然而,难点在于,从状态 [\textit{cur}] 出发的后缀链接应该连接到哪个状态呢?我们要把后缀链接连到一个状态上,且其中最长的一个字符串恰好是 [x+c] ,即这个状态的 [\operatorname{len}] 应该是 [\operatorname{len}(p)+1] 。然而还不存在这样的状态,即 [\operatorname{len}(q)>\operatorname{len}(p)+1] 。这种情况下,我们必须通过拆开状态 [q] 来创建一个这样的状态。