Perface
-
初二时学了一下SAM,当时以为自己懂了,后来仔细琢磨了一番,发现还有许多没有理解的地方。
-
于是有了这篇博客。
预备
-
有限状态自动机由五部分组成:alpha字符集,init初始状态,trans转移函数,state状态集合,end结束状态
-
S A M SAM SAM是一个有限状态自动机.
-
自动机的功能是识别字符串.
-
S A M ( x ) = T r u e SAM(x)=True SAM(x)=True表示 S A M SAM SAM能识别 x x x这个字符串.
-
S A M SAM SAM能识别一个字符串的所有后缀,也能识别子串.
-
t r a n s ( s , s t r ) trans(s,str) trans(s,str)表示当前状态是 s s s,读入字符串 s t r str str后到达的状态。为了方便,接下来用 S T ( s t r ) ST(str) ST(str)表示 t r a n s ( I n i t , s t r ) trans(Init,str) trans(Init,str)
-
r i g h t ( a ) right(a) right(a)表示 a a a这个字符串在母串 s s s中每一次出现位置的右端点集合.
-
F a c Fac Fac表示子串集合.
一些姿势
-
若 s ∈ F a c s\in Fac s∈Fac,则 S T ( s ) ≠ n u l l ST(s)\neq null ST(s)̸=null,否则 S T ( s ) = n u l l ST(s)=null ST(s)=null.
-
若 a , b ∈ F a c , r i g h t ( a ) = r i g h t ( b ) a,b\in Fac, right(a)=right(b) a,b∈Fac,right(a)=right(b),则 S T ( a ) = S T ( b ) ST(a)=ST(b) ST(a)=ST(b).
-
一个 r i g h t right right集 + 一个长度 l e n len len,唯一确立一个字符串 s ∈ F a c s\in Fac s∈Fac.
-
一个状态 s s s由所有 r i g h t right right集是 r i g h t ( s ) right(s) right(s)的字符串组成,也可以被理解为是一个子串的集合.
-
状态 s s s的子串集合中的长度可以表示为 [ M i n s , M a x s ] [Min_s,Max_s] [Mins,Maxs].
-
两个状态的 r i g h t right right集 R a , R b Ra,Rb Ra,Rb只有两种关系, R a ∩ R b Ra\cap R_b Ra∩Rb为空,或 R a ⊂ R b R_a\subset R_b Ra⊂Rb.
-
令一个状态 s s s的 f a s fa_s fas为满足 R i g h t ( s ) ⊂ R i g h t ( f a s ) Right(s)\subset Right(fa_s) Right(s)⊂Right(fas)且 R i g h t ( f a s ) Right(fa_s) Right(fas)最小的状态.
-
根据 f a s fa_s fas形成了一个树形结构,且状态数是线性的:
Proof
- 因为叶子结点个数是 O ( ∣ S ∣ ) O(|S|) O(∣S∣)的.
- 每个非叶子节点至少有两个孩子,否则可以合并.
- 所以总节点个数是 O ( ∣ S ∣ ) O(|S|) O(∣S∣)的.
-
M a x ( f a s ) = M i n ( s ) − 1 Max(fa_s)=Min(s)-1 Max(fas)=Min(s)−1
-
其实前面都是废话.接下来是重点。
一些性质
-
首先我们想证明的是 S A M SAM SAM中的边数是 O ( N ) O(N) O(N)级别的。构造法:
-
令状态数为 M M M,生成树中的边有 M − 1 M-1 M−1条,考虑非树边即可。
-
生成树中任意 (根到状态 a a a) +( a → b a\rightarrow b a→b) +( b b b到结束状态)对应一个后缀。
-
我们构造每一个后缀只对应一条非树边,且唯一对应。
-
-
如何让每一个后缀仅对应一条非树边, S A M SAM SAM采用增量法来构造.
增量法
-
假设现在已经构造了长为 ∣ S ∣ |S| ∣S∣的 S A M SAM SAM,现在要把 c c c字符加进去。
-
设 t r a n s ( I n i t , S ) = l a s t trans(Init,S)=last trans(Init,S)=last,新建节点 n p np np,则 l a s t last last必须向 n p np np连一条 c c c树边.
-
现在来连非树边.
-
f a l a s t fa_{last} falast如果没有 c c c字符的边,显然需要连一条非树边,因为 f a l a s t fa_{last} falast是 ∣ S ∣ |S| ∣S∣的一个后缀,后缀加上 c c c就是当前的后缀,如果之前没有出现过,必然要连一条非树边。
-
一个状态 s s s有一条标号为 x x x的边实际上等价于它的 r i g h t right right集中有 r r r满足 S [ r + 1 ] = x S[r+1]=x S[r+1]=x
-
沿着 f a fa fa走,直到某个 p p p拥有字符为 c c c的边,此时再往 f a fa fa走都一定拥有.
-
判断 s o n p , c son_{p,c} sonp,c与 p p p的 l e n len len的关系,并维护好 f a n p fa_{np} fanp即可.
-
值得一提的是其中的clone步骤,事实上这也是整个SAM中最神奇的地方。我们可以新建一个节点 n q nq nq来替代 s o n p , c son_{p,c} sonp,c。当然,替代的部分值得深思。我们想要的是 f a n p fa_{np} fanp,所以只有从 p p p直接连向 s o n p , c son_{p,c} sonp,c的这一条边需要转移到 n q nq nq来,然后把 f a n p = f a s o n p , c = n q , f a n q = p fa_{np}=fa_{son_{p,c}}=nq,fa_{nq}=p fanp=fasonp,c=nq,fanq=p即可。需要特别注意的是,如果 f a p fa_{p} fap还有连向 q q q的,都要转移到 n q nq nq来,原因是显然的。
-
至此完成了整个SAM的构造.