DFA
DFA,即确定性有限状态自动机,由一个五元组 M = ( Σ , Q , q s , F , t r ) M=(\Sigma,Q,q_s,F,tr) M=(Σ,Q,qs,F,tr)组成,其中:
Σ \Sigma Σ为一个有限字符集,其中每个字符 c c c称为一个输入符号;
Q Q Q为一个有限状态集合;
q s ∈ Q q_s\in Q qs∈Q为初始状态;
F ⊆ Q F\subseteq Q F⊆Q称为终结状态集合;
t r ∈ Q × Σ → Q tr\in Q\times \Sigma\to Q tr∈Q×Σ→Q称为状态转换函数。
t r ( q , c ) = q ′ tr(q,c)=q' tr(q,c)=q′表示当前状态为 q q q,输入符号为 c c c时,自动机 M M M将自动转换成下一个状态 q ′ q' q′,此时称 q ′ q' q′为 q q q的一个后继状态。
以上全是废话。
大家应该都知道AC自动机吧,如果不知道可以去看这一篇博客:http://blog.youkuaiyun.com/wang3312362136/article/details/78659403
一个DFA,就是一个有向图,有一个起点,有一些标有字符的边,有一个或多个终点,从这个起点,按照一个字符串上的字符,按顺序走与字符串的字符相同的边,最终走到了终点,就说明这个DFA能够识别这个字符;如果走到了非法状态 q ϕ q_\phi qϕ,或者走到了一个不是终点的状态,那么说明这个DFA不能识别这个字符串。
对于一个状态 p p p和 q q q,如果任何一个能够从 p p p转换到终点的字符串都可以从 q q q转换到终点,反之也成立,那么就说明 p p p和 q q q是等价状态,记为KaTeX parse error: Unexpected character: '' at position 2: p̲~q。
如果一个DFA中没有等价状态,那么这个自动机叫做最小状态自动机。
SAM
即后缀自动机,是能识别一个串所有后缀的最小状态自动机。
SAM的性质
它有哪些性质呢?
Theorem 1
对于任意一个 T ∈ Σ ∗ T\in \Sigma^* T∈Σ∗, t r ( q s , T ) ̸ = q ϕ tr(q_s,T)\not=q_\phi tr(qs,T)̸=qϕ当且仅当 T T T是 S S S的一个字串。
Lemma 2
设 T ∈ Σ ∗ T\in \Sigma^* T∈Σ∗是一个非空字符串, r i g h t ( T ) right(T) right(T)表示 T T T在 S S S中所有结束位置的集合,则有 L ( t r ( q s , T ) ) = { s u f r + 1 ∣ r ∈ r i g h t ( T ) } L(tr(q_s,T))=\{suf_{r+1}\mid r\in right(T)\} L(tr(qs,T))={sufr+1∣r∈right(T)}。
Theorem 3
设 T 1 T_1 T1, T 2 T_2 T2是 S S S的两个非空字符串,则 t r ( q s , T 1 ) = t r ( q s , T 2 ) tr(q_s,T_1)=tr(q_s,T_2) tr(qs,T1)=tr(qs,T2)的充要条件是 r i g h t ( T 1 ) = r i g h t ( T 2 ) right(T_1)=right(T_2) right(T1)=right(T2)。
Theorem 3.5
记一个状态 q q q的 r i g h t right right集合为 R q R_q Rq,则 r i g h t right right集合恰好为 R q R_q Rq的串,其长度一定在一个区间内,称之为 q q q的合法长度区间,记为 [ m i n l q , m a x l q ] [minl_q,maxl_q] [minlq,maxlq],对应从初始状态到这个状态的最短长度和最长长度。
Lemma 4
任取两个不同的状态 p , q p,q p,q,则下列三式中必有一式成立:
(1) R p ⋂ R q = ϕ R_p\bigcap R_q=\phi Rp⋂Rq=ϕ (2) R p ⊆ R q R_p\subseteq R_q Rp⊆Rq (3) R q ⊆ R p R_q\subseteq R_p Rq⊆Rp
Lemma 5
若一个状态 p p p是状态 p p p的 p a r e n t parent parent状态,当且仅当 R q R_q Rq是最小真包含 R p R_p Rp的集合,那么 q ∈ Q { q ϕ } q\in Q\text{\ }\{q_\phi\} q∈Q {qϕ}的 p a r e n t parent parent状态存在且唯一。
Theorem 6
设 q ∈ Q { q ϕ } q\in Q\text{\ }\{q_\phi\} q∈Q {qϕ}, p p p是 q q q的 p a r e n t parent parent状态,则有 m a x l p = m i n l q − 1 maxl_p=minl_q-1 maxlp=minlq−1,此时 p p p对应的所有字串都是 q q q的后缀。
所以,我们在后缀自动机中只需要存储每个节点 p a r e n t parent parent状态,和 m a x l maxl maxl值即可。
Theorem 7
对串 S S S构建后缀自动机 M M M,则有 M M M的状态数 ∣ Q ∣ ≤ 2 ∣ S ∣ + 1 |Q|\leq 2|S|+1 ∣Q∣≤2∣S∣+1。
Theorem 8
对串 S S S构建后缀自动机 M M M,则合法状态转换边的数量不超过 3 ∣ S ∣ 3|S| 3∣S∣。
- 后缀自动机中只需存储 S S S的字串对应的状态;
- 一个状态对应的字串,它们的 r i g h t right right集合相等,且长度取值必定对应一个区间;
- 每个状态与 p a r e n t parent parent状态的关系必定构成一棵 p a r e n t parent parent树;
- 一个状态的最小合法长度恰好比 p a r e n t parent parent状态的最大合法长度多 1 1 1;
- 后缀自动机是一个线性结构。
证明?
由自己的感觉可得
自己参阅资料吧。(其实可以背结论的)
SAM的构建
增量法,每次在后缀自动机中添加一个字符,然后对当前的SAM进行更新。
显然,添加一个字符并不会造成状态的合并,但有可能造成状态的分离,而分离的状态只有可能是在添加前后缀状态和非后缀状态都可以转换到它,此时,这个状态需要分裂成只能被后缀状态转换的状态和只能被非后缀状态转换的状态。(这个情况之后再讲)
设添加前这个串是 S S S,添加的字符为 c c c,设 t r ( q s , S ) = p tr(q_s,S)=p tr(qs,S)=p,由于所有后缀节点在 p a r e n t parent parent树上都是祖先关系,因此把它们记为 v 1 = p , v 2 , v 3 , ⋯   , v k = q s v_1=p,v_2,v_3,\cdots ,v_k=q_s v1=p,v2,v3,⋯,vk=qs。
由于
t
r
(
q
s
,
S
c
)
=
q
ϕ
tr(q_s,Sc)=q_\phi
tr(qs,Sc)=qϕ,那么我们需要新建一个节点
n
p
=
t
r
(
q
s
,
S
c
)
np=tr(q_s,Sc)
np=tr(qs,Sc)(这个是显然的)
那么
m
a
x
l
n
p
=
∣
S
∣
+
1
maxl_{np}=|S|+1
maxlnp=∣S∣+1,
R
n
p
′
=
{
n
+
1
}
R'_{np}=\{n+1\}
Rnp′={n+1}。
对于一个后缀状态 v v v,如果 t r ( v , c ) = q ϕ tr(v,c)=q_\phi tr(v,c)=qϕ(即 v v v没有符号为 c c c的合法转换边),那么将 t r ( v , c ) = n p tr(v,c)=np tr(v,c)=np即可,正确性显然。
设 v p v_p vp是 v 1 , v 2 , v 3 , ⋯   , v k v_1,v_2,v_3,\cdots,v_k v1,v2,v3,⋯,vk中第一个存在符号为 c c c的合法转换边的状态,那么记 q q q为 t r ( v p , c ) tr(v_p,c) tr(vp,c),取 r = R q r=R_q r=Rq中的最小值,此时记 Q 1 = S [ r , r ] , Q 2 = S [ r − 1 , r ] , ⋯   , Q r = S [ 1 , r ] Q_1=S[r,r],Q_2=S[r-1,r],\cdots,Q_r=S[1,r] Q1=S[r,r],Q2=S[r−1,r],⋯,Qr=S[1,r],下标就是串的长度。
由于 S [ r ] S[r] S[r]显然为 c c c,那么记 Q 1 = P 0 c , Q 2 = P 1 c , ⋯   , Q r = P r − 1 c Q_1=P_0c,Q_2=P_1c,\cdots,Q_r=P_{r-1}c Q1=P0c,Q2=P1c,⋯,Qr=Pr−1c,显然, P i = S [ r − i , r − 1 ] P_i=S[r-i,r-1] Pi=S[r−i,r−1]。那么状态 q q q对应的串就是 Q m i n l q , ⋯   , Q m a x l q Q_{minl_q},\cdots,Q_{maxl_q} Qminlq,⋯,Qmaxlq,所有能转换到 q q q的状态 p p p对应的串就是 S Q = { P m i n l q − 1 , ⋯   , P m a x l q − 1 } S_Q=\{P_{minl_{q}-1},\cdots,P_{maxl_q-1}\} SQ={Pminlq−1,⋯,Pmaxlq−1}。
又由于 v p v_p vp对应的后缀串只有 S P = { P 0 , ⋯   , P m a x l v p } S_P=\{P_{0},\cdots,P_{maxl_{v_p}}\} SP={P0,⋯,Pmaxlvp};
讨论 q q q的状态:
- 当 m a x l v p = m a x l q − 1 maxl_{v_p}=maxl_q-1 maxlvp=maxlq−1时,显然有 S Q ⊆ S P S_Q\subseteq S_P SQ⊆SP,那么,所有能通过输入符号 c c c转换到 q q q的状态都是后缀状态,此时 q q q不会发生状态的分裂。而对于 v p v_p vp在 p a r e n t parent parent树上的祖先,显然 t r ( v p , c ) tr(v_p,c) tr(vp,c)更不可能发生分裂。
- 当
m
a
x
l
v
p
<
m
a
x
l
q
−
1
maxl_{v_p}<maxl_q-1
maxlvp<maxlq−1时,
q
q
q的合法长度区间可以分成两个部分:
[
m
i
n
l
q
,
m
a
x
l
v
p
+
1
]
[minl_q,maxl_{v_p}+1]
[minlq,maxlvp+1]以及
[
m
a
x
l
v
p
+
2
,
m
a
x
l
q
]
[maxl_{v_p}+2,maxl_q]
[maxlvp+2,maxlq]。前者只有后缀状态能转移到它,后者只有非后缀状态能转移到它。
那么新建一个状态 n q nq nq,对应前者,新图中的 q ′ q' q′对应后者。由于 R n q R_{nq} Rnq显然只比 R q R_{q} Rq多一个 ∣ S ∣ + 1 |S|+1 ∣S∣+1,而 ∣ S ∣ + 1 |S|+1 ∣S∣+1之后并没有字符,没有状态能通过这个转移,那么 n q nq nq的状态转换边与 q q q完全相同。
而对于 v p v_p vp在 p a r e n t parent parent树上的祖先,显然,如果 t r ( v p , c ) = q tr(v_p,c)=q tr(vp,c)=q则指向 n q nq nq,否则不变。
对于 n p np np的 p a r e n t parent parent:当 v [ 1 , n ] v_{[1,n]} v[1,n]都不存在 c c c的转换边,说明 n p np np是第一次出现, p a r e n t parent parent是 q s q_s qs。否则, n p np np的 p a r e n t parent parent是 q q q或者 n q nq nq,取决于是否建立了 n q nq nq这个状态。
对于 q ′ q' q′和 n q nq nq的 p a r e n t parent parent:如果没有发生分裂,则不改变。如果发生了分裂,设原图中 q q q的 p a r e n t parent parent为 p a r q par_q parq,则 p a r q par_q parq, q q q和 n q nq nq这三个状态在 p a r e n t parent parent树上构成祖孙关系,进一步推理得 p a r n q = p a r q , p a r q ′ = n q par_nq=par_q,par_{q'}=nq parnq=parq,parq′=nq。
代码
struct samnode
{
samnode* tr[26];
samnode* par;
int maxl;
int init(int l=0)
{
memset(tr,0,sizeof tr);
par=NULL;
maxl=l;
return 0;
}
};
struct suffix_automaton
{
samnode* qs;
samnode* qlast;
samnode node[maxn*3];
int cntnode;
inline int clear()
{
delete qs;
delete qlast;
qs=new samnode;
qlast=new samnode;
qs->init();
qlast=qs;
cntnode=0;
return 0;
}
inline int addchr(int ch)
{
samnode* p=qlast;
samnode* np=&node[++cntnode];
np->init(p->maxl+1);
qlast=np;
while((p!=NULL)&&(p->tr[ch]==NULL))
{
p->tr[ch]=np;
p=p->par;
}
if(p==NULL)
{
np->par=qs;
return 0;
}
samnode* q=p->tr[ch];
if(p->maxl+1!=q->maxl)
{
samnode* nq=&node[++cntnode];
nq->init(p->maxl+1);
memcpy(nq->tr,q->tr,sizeof q->tr);
nq->par=q->par;
q->par=nq;
np->par=nq;
while((p!=NULL)&&(p->tr[ch]==q))
{
p->tr[ch]=nq;
p=p->par;
}
}
else
{
np->par=q;
}
return 0;
}
};