SAM
SAM
前言
过于神秘的字符串,但是被同学怂恿,说学会 SAM 就学会了所有字符串算法,心动了过来学,果不其然被创飞了。但研究?天,故余虽愚,终有所获。
由于作者学艺不精,文中可能会有疏漏,如发现错误欢迎指出,那么我们开始旅途吧。
这里是我看的四篇参考文章,质量挺高的:
1. 一些准备
概论
SAM 的定义:一个长为 n n n 的字符串 s s s 的 SAM 是一个接受 s s s 的所有后缀的最小的有限状态自动机(下文称为自动机)。
对于有限状态自动机,可以理解为一个 DAG
,即有向无环图,边上有字母,每走过一条边就相当于带上这个字母,每个节点存储一个或多个字符串。然后这个图有初始节点 P P P 和一个或多个终止节点。其它节点均可从 P P P 出发到达。
SAM 能表示某个字符串的所有子串。
但其实我们只要构造一个含有所有后缀的自动机,其就含有所有子串。
证明很简单,自动机都是一个个字符跳的,如果能跳到这个串,那么也一定经过了所有前缀。
不难有暴力,将原串的所有后缀插到一个字典树中就可以了,但是复杂度 O ( n 2 ) O(n^2) O(n2) 级别,非常不好。
记号
在下文中,字符串的下标从 1 1 1 开始。
- 记 s i s_i si 或 s [ i ] s[i] s[i] 为字符串 s s s 的第 i i i 位。
- 记 s [ l , r ] s_{[l,r]} s[l,r] 或 s [ l , r ] s[l,r] s[l,r] 表示 s s s 中下标从 l l l 到 r r r 的子串。
- 记 n n n 或 ∣ s ∣ |s| ∣s∣ 为字符串 s s s 的长度。
2. e n d p o s \mathrm{endpos} endpos
概述
我们定义一个子串的 e n d p o s \mathrm{endpos} endpos 是它在原串中结束位置的集合。
例如原串为 a b a b c \mathrm{ababc} ababc,则 e n d p o s ( a b ) = { 2 , 4 } , e n d p o s ( c ) = { 5 } \mathrm{endpos(ab)}=\{2,4\},\mathrm{endpos(c)}=\{5\} endpos(ab)={ 2,4},endpos(c)={ 5}
我们把 e n d p o s \mathrm{endpos} endpos 相同的的子串称为一个等价类。如 e n d p o s ( b ) \mathrm{endpos(b)} endpos(b) 和 e n d p o s ( a b ) \mathrm{endpos(ab)} endpos(ab) 相同,它们属于同一个等价类。
下文中用 e d p edp edp 代表 e n d p o s \mathrm{endpos} endpos
性质
然后有一些性质。
下面设两个字符串分别为 s 1 , s 2 s1,s2 s1,s2,且约定 ∣ s 1 ∣ ≤ ∣ s 2 ∣ |s1| \le|s2| ∣s1∣≤∣s2∣。
引理 2.1 2.1 2.1
s 1 s1 s1 是 s 2 s2 s2 的后缀 ⟺ e d p ( s 2 ) ⊆ e d p ( s 1 ) \Longleftrightarrow edp(s2)\subseteq edp(s1) ⟺edp(s2)⊆edp(s1)
这点显然, s 1 s1 s1 是 s 2 s2 s2 的后缀,每次出现 s 2 s2 s2 时必定出现 s 1 s1 s1,但出现 s 1 s1 s1 时不一定出现 s 2 s2 s2,得证。
引理 2.2 2.2 2.2
要么有 e d p ( s 2 ) ⊆ e d p ( s 1 ) edp(s2)\subseteq edp(s1) edp(s2)⊆edp(s1),要么有 e d p ( s 2 ) ∩ e d p ( s 1 ) = ∅ edp(s2)\cap edp(s1)=\varnothing edp(s2)∩edp(s1)=∅
这点也可以看出,引理 2.1 2.1 2.1 证明了前半部分,对于后半部分考虑反证,若存在 i i i 为公共部分,则可以推出 s 1 s1 s1 是 s 2 s2 s2 的后缀。不存在这样的 i i i 则可以说明 s 1 s1 s1 不是 s 2 s2 s2 的后缀。所以这个分类正确。
引理 2.3 2.3 2.3
一个等价类中不会包括两个本质不同而长度相同的串。
考虑反证。
假设存在一个等价类 [ s ] [s] [s],其中包含两个本质不同但长度相同的串