一篇并不对劲的后缀自动机教程
说是教程其实也就我自己看怕我这个shabi又双叒叕忘了后缀自动机然后再学一遍
1.后缀自动机就是能识别字符串S所有后缀的自动机
根据定义知道 它也可以识别S的所有子串
2.Right集合
是指子串str在母串S中出现的结束位置的集合
对于一个Right集合,适合它的子串长度取值区间为$[minlen(s),maxlen(s)]$
3.parent树
根据上面Right集合的定义,我们可以按Right集合的包含关系建一棵树,就是parent树
父亲的Right集合包含所有儿子的Right集合
很容易知道parent树从上到下子串长度边长,Right集合变小
我们令fa = parent(s)则可发现
$Right(s)⊂Right(fa)$且$Right(fa)$最小
发现$maxlen(fa)=minlen(s)−1$
parent树可以相当于fail树,一个单词匹配不上的时候可以沿着parent树往上跳
4.maxlen
根据Right集合的定义,一个点的maxlen其实就是转移图上root到x的最长路径长度
顺便,minlen(x)是最短的
5.其他可用的性质
I.把maxlen基数排序,就得到了转移图的拓扑序
II.在SAM上两个串的最长公共子串就是这两个点的LCA
III.由于SAM的存在,我们可以把序列的东西强行上树,然后把树的东西强行上字符串,所以如果您做到SAM+树链剖分/SAM+LCT的时候,不要过早骂人
struct SAM { int tr[maxn][26],fa[maxn],len[maxn],size[maxn]; int ST[maxn][21],id[maxn],Cnt[maxn]; int last,p,np,q,nq,cnt,rt; SAM(){last = ++cnt; rt = 1;} inline void extend(int c) { p = last, np = last = ++cnt, len[np] = len[p] + 1;size[np] = 1; while(!tr[p][c] && p) tr[p][c] = np, p = fa[p]; if(!p) fa[np] = rt; else { q = tr[p][c]; if(len[q] == len[p] + 1) fa[np] = q; else { nq = ++cnt; len[nq] = len[p] + 1; memcpy(tr[nq],tr[q],sizeof(tr[q])); fa[nq] = fa[q]; fa[q] = fa[np] = nq; while(tr[p][c] == q) tr[p][c] = nq,p = fa[p]; } } } inline void buildsize() { for(int i=1;i<=cnt;i++)Cnt[len[i]]++; for(int i=1;i<=n;i++)Cnt[i] += Cnt[i-1]; for(int i=1;i<=cnt;i++)id[Cnt[len[i]]--] = i; for(int i=cnt;i>=1;i--)size[fa[id[i]]] += size[id[i]]; } }sam;