前言
其实我理解的不是很深刻,有大佬发现错误的请指教
自动机
什么是自动机?
有限状态自动机:能够识别一个字符串
后缀自动机?
能识别一个串的所有后缀(其实可以识别所有子串)
瞎扯和分析
首先,一个状态ST()表示一个字符串。
设要分析的字符串为S,suf为后缀集合
对于属于串S的每一个子串s1,s1需要开一个状态,因为后面加上一些字符后可能成为后缀
先假设对于每个子串s1,开一个状态
我们需要让s1被自动机识别,当且仅当
s1∈suf
s1可能在S中出现多次,设为[li~ri]
记ST(s1)的right集为ri的集合,即
right(ST(s1))={ri|s1=S[li,ri]}
在知道s1的情况下,可以知道right()
那么在知道状态x和right(x)的情况下,也可以知道它所代表的子串。
对于x,记一个len,表示这个状态表示的子串的长度。
那么x所代表的子串就是
S[ri−len+1,ri],ri∈right(x)
目前,我们对于所有子串都记录了一个状态,现在,考虑合并。
right()集是一个很重要的东西,所以合并就把所有right()集相同的状态合成一个。
这样会有所不同,原来一个状态x,记录了len表示子串长度,现在很多状态合起来了,子串长度也变成了区间,记为[min(x),max(x)]。
举个例子:
串S:ABBABB,s1:ABB,s2:BB
那么
right(st(s1))={3,6},len=3
right(st(s2))={3,6},len=2
发现完全一样
那么就合并起来,len变成区间[2,3]
一个性质:合并后两个状态的right集要么不相交,要么一个是另一个的真子集
证明:
假设两个状态a,b,right集相交
由定义知:两个状态代表的子串中没有相同的
即
[min(a),max(a)]与[min(b),max(b)]无交集
那么假设
max(a)<min(b)
,那么a中所有串比b中短,右端点又相同,那么a中的串都是b中的串的后缀,所以b真包含a,得证
同时可以知道,合并后的状态数量为O(n)
构造一棵树,树上一个点x,父亲为y,满足:
right(x)⊂right(y)
且|right(y)|最小
这棵树叫fail树或parent树
根据right的性质,每个点必有至少两个儿子,叶节点有n个
所以大小是O(n)的
性质:max[y]=min[x]-1
因为一个状态代表的子串的长度有下限,超过下限的是因为有些位置超过了right(x),这些位置必然在y里面
状态合并后,得到的是一个DAG,而一条从起点到终点集中的一个点的路径代表了一个后缀
一个子串会包含在某个状态里,所以SAM也可以识别子串,子串出现个数就是right集大小
right集不能直接记录,在fail树上一个叶子节点的right集大小是1,而right集不是包含就是无交,所以记录下叶子上的就行了
构造
回顾一下:
right集:相同字符串右端点的集合
right集两两包含或无交
状态x是right集相同的子串合并起来,长度是一个区间[min,max]
fail树上max[fa[x]]=min[x]-1
构造的方法是增量法(好像很多字符串有关的东西都是这个)
因为有max[fa[x]]=min[x]-1所以只记录max
目前已经有一个字符串S,要加入一个字符c
设上次加入的状态为last,p=last
新增一个状态np,len(np)=len(p)+1,因为加入了一个字符
沿p的fail链走,走到的点如果没有c这个儿子,就连到np
p走到第一个有c的点,设c那条边连接q
假设max[q]>max[p]+1
会发现如果将fail[np]=q即将新的这个点加入q的right集,会有max[p]+2<=len<=max[q],len是插入的一个串,也就是说这个串在np中已经出现了,不能让一个串出现在两个状态中
而max[q]=max[p]+1时就不会出现,所以这时可以直接fail[np]=q
所以可以设一个nq,right(nq)表示right(q)与right(np)的并集,而max(nq)=max(p)+1,fail(q)=fail(np)=nq,这样加入了一部分新后缀,重复的就不会加入了
(难理解)
代码与题目
void add(int x)
{
int p=last,np=++tot;
t[np].len=t[p].len+1;t[np].size=1;last=np;
for(;p&&t[p].to[x]==0;p=t[p].fail) t[p].to[x]=np;
if(p==0){t[np].fail=1;return;}
int q=t[p].to[x];
if(t[p].len+1==t[q].len) t[np].fail=q;
else
{
int nq=++tot;t[nq]=t[q];
t[nq].len=t[p].len+1;t[q].fail=t[np].fail=nq;
for(;p&&t[p].to[x]==q;p=t[p].fail) t[p].to[x]=nq;
}
}