DFA
DFA,即确定性有限状态自动机,由一个五元组组成,其中:
为一个有限字符集,其中每个字符称为一个输入符号;
为一个有限状态集合;
为初始状态;
称为终结状态集合;
称为状态转换函数。
表示当前状态为,输入符号为时,自动机将自动转换成下一个状态,此时称为的一个后继状态。
以上全是废话。
大家应该都知道AC自动机吧,如果不知道可以去看这一篇博客:http://blog.csdn.net/wang3312362136/article/details/78659403
一个DFA,就是一个有向图,有一个起点,有一些标有字符的边,有一个或多个终点,从这个起点,按照一个字符串上的字符,按顺序走与字符串的字符相同的边,最终走到了终点,就说明这个DFA能够识别这个字符;如果走到了非法状态,或者走到了一个不是终点的状态,那么说明这个DFA不能识别这个字符串。
对于一个状态和,如果任何一个能够从转换到终点的字符串都可以从转换到终点,反之也成立,那么就说明和是等价状态,记为KaTeX parse error: Unexpected character: '' at position 2: p̲~q。
如果一个DFA中没有等价状态,那么这个自动机叫做最小状态自动机。
SAM
即后缀自动机,是能识别一个串所有后缀的最小状态自动机。
SAM的性质
它有哪些性质呢?
Theorem 1
对于任意一个,当且仅当是的一个字串。
Lemma 2
设是一个非空字符串,表示在中所有结束位置的集合,则有。
Theorem 3
设,是的两个非空字符串,则的充要条件是。
Theorem 3.5
记一个状态的集合为,则集合恰好为的串,其长度一定在一个区间内,称之为的合法长度区间,记为,对应从初始状态到这个状态的最短长度和最长长度。
Lemma 4
任取两个不同的状态,则下列三式中必有一式成立:
(1) (2) (3)
Lemma 5
若一个状态是状态的状态,当且仅当是最小真包含的集合,那么的状态存在且唯一。
Theorem 6
设,是的状态,则有,此时对应的所有字串都是的后缀。
所以,我们在后缀自动机中只需要存储每个节点状态,和值即可。
Theorem 7
对串构建后缀自动机,则有的状态数。
Theorem 8
对串构建后缀自动机,则合法状态转换边的数量不超过。
- 后缀自动机中只需存储的字串对应的状态;
- 一个状态对应的字串,它们的集合相等,且长度取值必定对应一个区间;
- 每个状态与状态的关系必定构成一棵树;
- 一个状态的最小合法长度恰好比状态的最大合法长度多;
- 后缀自动机是一个线性结构。
证明?
由自己的感觉可得
自己参阅资料吧。(其实可以背结论的)
SAM的构建
增量法,每次在后缀自动机中添加一个字符,然后对当前的SAM进行更新。
显然,添加一个字符并不会造成状态的合并,但有可能造成状态的分离,而分离的状态只有可能是在添加前后缀状态和非后缀状态都可以转换到它,此时,这个状态需要分裂成只能被后缀状态转换的状态和只能被非后缀状态转换的状态。(这个情况之后再讲)
设添加前这个串是,添加的字符为,设,由于所有后缀节点在树上都是祖先关系,因此把它们记为。
由于,那么我们需要新建一个节点(这个是显然的)
那么,。
对于一个后缀状态,如果(即没有符号为的合法转换边),那么将即可,正确性显然。
设是中第一个存在符号为的合法转换边的状态,那么记为,取中的最小值,此时记,下标就是串的长度。
由于显然为,那么记,显然,。那么状态对应的串就是,所有能转换到的状态对应的串就是。
又由于对应的后缀串只有;
讨论的状态:
- 当时,显然有,那么,所有能通过输入符号转换到的状态都是后缀状态,此时不会发生状态的分裂。而对于在树上的祖先,显然更不可能发生分裂。
- 当时,的合法长度区间可以分成两个部分:以及。前者只有后缀状态能转移到它,后者只有非后缀状态能转移到它。
那么新建一个状态,对应前者,新图中的对应后者。由于显然只比多一个,而之后并没有字符,没有状态能通过这个转移,那么的状态转换边与完全相同。
而对于在树上的祖先,显然,如果则指向,否则不变。
对于的:当都不存在的转换边,说明是第一次出现,是。否则,的是或者,取决于是否建立了这个状态。
对于和的:如果没有发生分裂,则不改变。如果发生了分裂,设原图中的为,则,和这三个状态在树上构成祖孙关系,进一步推理得。
代码
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;
}
};