SAM[详细~bushi]
基础性质概念
- 后缀自动机:S的SAM是个DAG,每个节点叫状态,每条带字符ch边表示+ch转移,从开始节点往下,任何一条路径都会对应一个S的子串。
不过为什么要叫"后缀"自动机呢?
- endpos集合:endpos(s)={s的所有右端点}[s为S的一个子串]
- 状态(节点):把endpos{}相同的等价类归为一个状态。
- 边(ch[u][x]):上面定义也说过,状态u中每个s+x集合构成的状态。
- 推论1:一个等价类中的两个s满足后缀关系。如果不满足,那么存在endpos{}不同。
- 推论2:由推论1易得等价类中所有s都互相满足前后缀关系而且它们的长度是连续的[l..l+1..r]。假如不连续,存在x,y,z(x是y,z的后缀,y是z的后缀),(endpos简称)ep{x}=ep{z}!=ep{y}所以ep{x}包含ep{y},ep{y}包含ep{z},因此ed{x}=ed{y}=ed{z},与假设矛盾。
- len[]:表示状态中s最长长度,最短的可以用接下来的变量计算。
- 推论3:两个等价类的集合要么包含,要么不交,取决与是否有前后缀关系。如果有前后缀关系,len大的那个可以看作len小的那个加一堆前缀很好解释len小包含len大。然后没有后缀关系显然不能有交。
推论2,3也让我们从后缀角度看待状态。
或许你认为以上就能构成后缀自动机。按照定义来说是的,但是不够。我们还要保证它的状态数和构建操作数为\(O(n)\)
接下来我们会在SAM上连一些虚边,这些虚边会构成有用的fail树。
- par[]:严格包含其ep的最小ep状态点,即par[u]包含的s'是u中s的后缀(而且连续),mnlen[par[u]]=len[u]+1
- Fail树:father[]为par的一棵树
感性来理解就像某前缀的后缀被分成了很多状态连成一条链。
构建
叫什么增量法,就是在线字符一个一个地加。然后找其最大后缀(par[])。
前提(当前加入第i个,字符为x,这个前缀i的节点为np)
ps.可结合代码理解
首先last为前缀i-1所在的节点。p=last往fail树祖先上跳,同时更新ch[p][x],直到找到一个点ch[p][x]!=0,此时前缀i的最长后缀的长度就是len[p]+1。(因为(在p下面)比len[p]长的后面都没有x)
当然没有这么简单,我们要分三类讨论:
1.没有找到par[],par[np]=rt直接结束
另q=ch[p][x]
2.len[q]=len[p]+1直接par[np]=q
3.len[q]>len[p]+1所代表后缀字符集可以分成两类。长度<=len[p]+1的一类(可以作i前缀(np)的后缀),>len[p]+1的一类跟这次加入无关。
所以容易想到把q拆成两个点,其实拆出1个新点nq(第1类),原来的q(第2类),首先不修改q,用q原来的信息更新nq(par[],ch[][]),len[nq]=len[p]+1,然后更新par[q]=par[p]=nq(Fail树中把q,np连在nq下)。
最后容易遗忘的是p及其上面一些点(因为len[nq]=len[p]+1)原来ch[p][x]=q现在改为ch[p][x]=nq了。
复杂度
可以看看别人的博客
不会证耶?有会证的而且证的很简单的私信我?
code
struct SAM {
int ch[N][27],t[N],len[N],lst,num,par[N],sz[N];
SAM() {lst=num=1;}
int Insert(int x) {
int p=lst,np=++num;lst=np;len[np]=len[p]+1;sz[np]=1;
for(;!ch[p][x];p=par[p]) ch[p][x]=np;
if(!p) {par[np]=1;return np;}
int q=ch[p][x];
if(len[q]==len[p]+1) {par[np]=q;return np;}
int nq=++num;par[nq]=par[q];len[nq]=len[p]+1;
for(int j=0;j<26;j++)ch[nq][j]=ch[q][j];
par[q]=par[np]=nq;
for(;ch[p][x]==q;p=par[p])ch[p][x]=nq;
return np;
}
}A;
用途(例题)
匹配字符串:
不用说了所有自动机(dfn)都能做的事情
多少个本质不同的字符串(位置不同串相同算同一个)
\(ans= \sum len[u]-len[fa[u]]\)每个(本质不同)字符串都唯一对应与SAM中的节点,而SAM中不同节点表示的字符集没有交集。因此就是每个节点u表示的字符个数(len[par[u]]-len[u])的和。
每个字符串出现了几次
Fail树上,每个节点\(sz[par[u]]+=sz[u]\),每个前缀对应节点(\(sz[np]=1\))
其实sz[u]就是ep等价类u中字符串对应的出现次数了
证明?
把前缀np,sz[np]=1,实际上它会给所有它的后缀+1,然后每个节点对应等价类的ep{r1,r2,r3..rk},显然sz=k。其中每个都会被前缀r1,前缀r2..前缀rk+1各+1。所以刚好+k次,得证。