后缀自动机 SAM \text{SAM} SAM .
仅为学习笔记,思路会跟博客 后缀自动机(SAM)学习笔记 - zjp_shadow - 博客园 比较类似 .
定义 e n d p o s ( t ) endpos(t) endpos(t) 为子串 t t t 在 s s s 中出现过的所有结束位置的并 .
阅读下文时建议将 状态 看成是 e n d p o s endpos endpos 的一个集合 .
有时也会用 parent树 \text{parent树} parent树 来代替 后缀树 \text{后缀树} 后缀树 .
Lemma
对于两个子串 s 1 s1 s1 和 s 2 s2 s2 ,若 ∣ s 1 ∣ ≤ ∣ s 2 ∣ |s1|\le |s2| ∣s1∣≤∣s2∣ ,则 s 1 s1 s1 是 s 2 s2 s2 的后缀当且仅当 e n d p o s ( s 1 ) ⊇ e n d p o s ( s 2 ) endpos(s1)\supseteq endpos(s2) endpos(s1)⊇endpos(s2) , s 1 s1 s1 不是 s 2 s2 s2 的后缀当且仅当 e n d p o s ( s 1 ) ∩ e n d p o s ( s 2 ) = ∅ endpos(s1)\cap endpos(s2)=\empty endpos(s1)∩endpos(s2)=∅ .
包含的前提保证 s 2 s2 s2 出现的位置都会有 s 1 s1 s1 出现,由结尾位置相同并且 ∣ s 1 ∣ ≤ ∣ s 2 ∣ |s1|\le |s2| ∣s1∣≤∣s2∣ 可知 s 1 s1 s1 是 s 2 s2 s2 后缀 .
反之即 e n d p o s ( s 1 ) ∩ e n d p o s ( s 2 ) = ∅ endpos(s1)\cap endpos(s2) = \empty endpos(s1)∩endpos(s2)=∅ .
Characters
以 s = aabbabd \text{s}=\text{aabbabd} s=aabbabd 为例 .
- SAM \text{SAM} SAM 中的一个状态包含的子串都具有相同的 e n d p o s endpos endpos ,并且它们互为后缀 .
一个状态指的是从起点出发到这个点的所有路径组成的子串的并 .
例子中状态 4 4 4 为 { bb , aabb , abb } \{\text{bb} \ , \ \text{aabb}\ , \ \text{abb}\} {bb , aabb , abb} .
对于一个状态 st \text{st} st , 记 S ( st ) S(\text{st}) S(st) 为状态 4 4 4 的所有子串集合, l ( st ) l(\text{st}) l(st) 为其中最长的子串, s h ( st ) sh(\text{st}) sh(st) 为其中最短的子串 .
例子中状态 4 4 4 中 l ( 4 ) = aabb l(4)=\text{aabb} l(4)=aabb , s h ( 4 ) = bb sh(4)=\text{bb} sh(4)=bb .
- 对于任意 t ∈ S ( st ) \text{t}\in S(\text{st}) t∈S(st) ,都有 e n d p o s ( t ) ⊇ e n d p o s ( l ( st ) ) endpos(t)\supseteq endpos(l(\text{st})) endpos(t)⊇endpos(l(st)) .
- S ( st ) S(\text{st}) S(st) 包含的是 l ( st ) l(\text{st}) l(st) 的一系列后缀 .
后缀链接
考虑状态 7 7 7 , S ( 7 ) = { aabbab , abbab , bbab , bab } S(7)=\{\text{aabbab}\ , \ \text{abbab} \ , \ \text{bbab} \ , \ \text{bab}\} S(7)={aabbab , abbab , bbab , bab} ,接下来的一个后缀 a b ab ab 由于 e n d p o s ( ab ) = { 3 , 6 } endpos(\text{ab})=\{3\ , \ 6\} endpos(ab)={3 , 6} ,不完全等于状态 7 7 7 ( e n d p o s ( t ∣ t ∈ S ( 7 ) ) = 6 endpos(t\ |\ t\in S(7))=6 endpos(t ∣ t∈S(7))=6) ,因此 ab \text{ab} ab 不在状态 7 7 7 中,称这种情况叫做 “断掉” .
当 l ( st ) l(\text{st}) l(st) 的某个后缀在新的位置中出现时,就会 “断掉” , 这个后缀 s s s 就会属于一个新的状态 .
上面的例子中 ab \text{ab} ab 属于 8 8 8 ,接着考虑 b \text{b} b 会发现 e n d p o s ( b ) = { 3 , 4 , 6 } endpos(\text{b}) = \{3\ , \ 4\ , \ 6\} endpos(b)={3 , 4 , 6} , b \text{b} b 属于状态 5 5 5 ,在接下来 e n d p o s ( ∅ ) = { 0 , 1 , 2 , 3 , 4 , 5 , 6 } endpos(\empty) = \{0\ , \ 1\ , \ 2\ , \ 3\ , \ 4\ , \ 5\ , \ 6\} endpos(∅)={0 , 1 , 2 , 3 , 4 , 5 , 6} , ∅ \empty ∅ 属于状态 S S S .
顺着走下来可以发现一条状态路径 $7\rightarrow 8 \rightarrow 5 \rightarrow S $ ,这个序列的意义为 l ( 7 ) l(7) l(7) 的后缀依次出现在状态 7 7 7 , 8 8 8 , 5 5 5 , S S S 中 .我们用后缀链接 SuffixLink \text{SuffixLink} SuffixLink 来把它们连接起来,对应上图的虚线.
转移函数
记 Next ( s ) \text{Next}(s) Next(s) 为以状态 s s s 开始下一个可能会遇到的字符集,即 Next ( st ) = { s [ i + 1 ] ∣ i ∈ e n d p o s ( st ) } \text{Next}(\text{st}) = \{s[i+1]\ | \ i\in endpos(\text{st})\} Next(st)={s[i+1] ∣ i∈endpos(st)} .
例子中 Next ( S ) = { s [ 0 ] , … , s [ 6 ] } = { a , b , d } \text{Next}(S)=\{s[0]\ , \ \ldots\ , \ s[6]\}=\{\text{a}\ , \ \text{b}\ , \ \text{d}\} Next(S)={s[0] , … , s[6]}={a , b , d} .
Next ( 8 ) = { b , d } \text{Next}(8) = \{\text{b}\ , \ \text{d}\} Next(8)={b , d} .
很显然, S ( st ) S(\text{st}) S(st) 后面接上一个 c ∈ Next ( st ) c\in \text{Next}(\text{st}) c∈Next(st) 会变成同一个状态,记这个状态为 trans ( st , c ) \text{trans}(\text{st}\ ,\ c) trans(st , c) .
构造
我们使用 增量法 来进行构造 .
记录一下数据:
数据 | 含义 |
---|---|
m a x l e n [ s t ] maxlen[st] maxlen[st] | s t st st 包含的最长子串长度 |
m i n l e n [ s t ] minlen[st] minlen[st] | s t st st 包含的最短子串长度 |
t r a n s [ s t ] [ 1 … c ] trans[st][1\ldots c] trans[st][1…c] | s t st st 的转移函数,其中 1 … c 1\ldots c 1…c 为字符集 |
l i n k [ s t ] link[st] link[st] | s t st st 的后缀链接 |
考虑现在新加入的字符 s [ i + 1 ] s[i+1] s[i+1] ,设 s [ 1 … i ] s[1\ldots i] s[1…i] 的状态为 u u u ,即 s [ 1 … i ] ∈ S ( u ) s[1\ldots i]\in S(u) s[1…i]∈S(u) .
于是 s [ 2 … i ] , s [ 3 … i ] , … , s [ i ] , ∅ s[2\ldots i]\ , \ s[3\ldots i]\ , \ \ldots \ , \ s[i]\ , \ \empty s[2…i] , s[3…i] , … , s[i] , ∅ 的所属状态即为沿着 SuffixLink ( u ) \text{SuffixLink}(u) SuffixLink(u) 走的路径,记作 Suffix-path ( u → S ) \text{Suffix-path}(u\rightarrow S) Suffix-path(u→S) .
显然对于 s [ 1 … i ] = l ( u ) ∈ S ( u ) s[1\ldots i]= l(u)\in S(u) s[1…i]=l(u)∈S(u) ,其任意后缀 t t t 要么在状态 u u u 里,要么在 Suffix-path ( u → S ) \text{Suffix-path}(u\rightarrow S) Suffix-path(u→S) 上 .
由于 s [ 1 … i + 1 ] s[1\ldots i+1] s[1…i+1] 不在任一已知状态中,因此开一个新的状态 z z z 来表示它 .
接下来考虑两种情况:
- 简单一点的,对于 Suffix-path ( u → S ) \text{Suffix-path}(u\rightarrow S) Suffix-path(u→S) 中的任意状态 v v v ,都有 t r a n s [ v ] [ s [ i + 1 ] ] = ∅ trans[v][s[i+1]]=\empty trans[v][s[i+1]]=∅ ,这时只需要令 t r a n s [ v ] [ s [ i + 1 ] ] = z trans[v][s[i+1]]=z trans[v][s[i+1]]=z ,并且 l i n k [ z ] = S link[z] = S link[z]=S 即可 .
在这个例子中, u = 2 u=2 u=2 , z = 3 z=3 z=3 , Suffix-path ( u → S ) \text{Suffix-path}(u\rightarrow S) Suffix-path(u→S) 是 桔色状态 \color{orange}{\text{桔色状态}} 桔色状态 组成的路径 2 → 1 → S 2\rightarrow 1\rightarrow S 2→1→S ,并且这三个状态都没有 b \text{b} b 的转移,因此只需添加 红色状态 \color{red}{\text{红色状态}} 红色状态 t r a n s [ 2 ] [ 1 ] = 3 trans[2][1] = 3 trans[2][1]=3 , t r a n s [ 1 ] [ 1 ] = 3 trans[1][1] = 3 trans[1][1]=3 , t r a n s [ S ] [ 1 ] = 3 trans[S][1] = 3 trans[S][1]=3 即可 ,当然还要 l i n k [ 3 ] = S link[3] = S link[3]=S .
- 难搞一点的为 Suffix-path ( u → S ) \text{Suffix-path}(u\rightarrow S) Suffix-path(u→S) 中有一个状态 v v v ,使得 t r a n s [ v ] [ s [ i + 1 ] ] ≠ ∅ trans[v][s[i+1]]\not= \empty trans[v][s[i+1]]=∅ .
上面的例子中已经构造出了 aabb \text{aabb} aabb 的 SAM \text{SAM} SAM ,现在要添加一个字符 a \text{a} a 构造 aabba \text{aabba} aabba 的 SAM \text{SAM} SAM .
这时 u = 4 u=4 u=4 , z = 6 z=6 z=6 , Suffix-path ( u → S ) \text{Suffix-path}(u\rightarrow S) Suffix-path(u→S) 为 桔色状态 \color{orange}{\text{桔色状态}} 桔色状态 组成的路径 4 → 5 → S 4\rightarrow 5\rightarrow S 4→5→S ,对于 4 4 4 和 5 5 5 都有 t r a n s [ 4 ] [ 0 ] = ∅ trans[4][0] = \empty trans[4][0]=∅ , t r a n s [ 5 ] [ 0 ] = ∅ trans[5][0]=\empty trans[5][0]=∅ ,因此直接添加 红色转移 \color{red}{\text{红色转移}} 红色转移 t r a n s [ 4 ] [ 0 ] = t r a n s [ 5 ] [ 0 ] = 6 trans[4][0] = trans[5][0] = 6 trans[4][0]=trans[5][0]=6 即可,但此时 t r a n s [ S ] [ 0 ] = 1 trans[S][0]=1 trans[S][0]=1 已经存在了 .
不失一般性的,设 Suffix-path ( u → S ) \text{Suffix-path}(u\rightarrow S) Suffix-path(u→S) 中遇到的第一个状态 v v v 满足 t r a n s [ v ] [ s [ i + 1 ] ] = x trans[v][s[i + 1]] = x trans[v][s[i+1]]=x ,接下来讨论 x x x 包含的的子串情况 .
- 如果 x x x 中包含的最长子串就是 v v v 中最长子串接上 s [ i + 1 ] s[i + 1] s[i+1] 后的子串,即 m a x l e n [ v ] + 1 = m a x l e n [ x ] maxlen[v] +1= maxlen[x] maxlen[v]+1=maxlen[x] ,这种情况比较简单,则只需增加 l i n k [ z ] = x link[z] = x link[z]=x 即可 .
例子中,只需让 l i n k [ 6 ] = 1 link[6]=1 link[6]=1 即可.
- 如果 x x x 中包含的最长子串不是 v v v 中最长子串接上 s [ i + 1 ] s[i+1] s[i+1] 后的子串,即 m a x l e n [ v ] + 1 < m a x l e n [ x ] maxlen[v]+1 < maxlen[x] maxlen[v]+1<maxlen[x] ,这种情况是重点讨论对象 .
在 Suffix-path ( u → S ) \text{Suffix-path}(u\rightarrow S) Suffix-path(u→S) 这条路径上,从 u u u 开始有一部分连续的状态满足 t r a n s [ st ] [ c ] = ∅ trans[\text{st}][\text{c}]=\empty trans[st][c]=∅ ,则令 t r a n s [ st ] [ c ] = z trans[\text{st}][\text{c}]=z trans[st][c]=z 即可 .
紧接着有一部分连续的状态满足 t r a n s [ v … w ] [ c ] = x trans[v\ldots w][\text{c}]=x trans[v…w][c]=x ,并且 l ( v ) + c ≠ l ( x ) l(v)+\text{c}\not= l(x) l(v)+c=l(x) .
这时我们就从 x x x 中拆出新的状态 y y y ,并且把所有原本属于 x x x 的满足长度小于 m a x l e n [ v ] + 1 maxlen[v]+1 maxlen[v]+1 的子串分给 y y y ,其余的子串留给 x x x ,同时令 t r a n s [ v … w ] [ c ] = y trans[v\ldots w][\text{c}] = y trans[v…w][c]=y , l i n k [ y ] = l i n k [ x ] link[y]=link[x] link[y]=link[x] , l i n k [ x ] = l i n k [ z ] = y link[x] = link[z] = y link[x]=link[z]=y .
也就是 y y y 继承 x x x 的后缀链接,并且 x x x 和 z z z 前面断开的 S S S 就存在于 y y y 中了 .
在这个例子中,先前已经够早了 aab \text{aab} aab 的 SAM \text{SAM} SAM ,接下来要构造 aabb \text{aabb} aabb 的 SAM \text{SAM} SAM .
由 u = 3 u=3 u=3 , z = 4 z=4 z=4 ,处理 S S S 时遇到 t r a n s [ S ] [ 1 ] = 3 trans[S][1] = 3 trans[S][1]=3 , l ( 3 ) = aab l(3)=\text{aab} l(3)=aab , l ( S ) + b = b ≠ l ( 3 ) l(S)+\text{b}=\text{b}\not= l(3) l(S)+b=b=l(3) ,两者不相等 ,意味着 e n d p o s ( aab ) endpos(\text{aab}) endpos(aab) 已经不等于 e n d p o s ( b ) endpos(\text{b}) endpos(b) 了,势必这两个子串不能属于同一个状态 3 3 3 .
这时我们就需要从状态 3 3 3 中拆出一个状态 5 5 5 , 把 b \text{b} b 以及其后缀分给 5 5 5 , 其余的子串留给 3 3 3 . 同时令 t r a n s [ S ] [ 3 ] = 5 trans[S][3]=5 trans[S][3]=5 , l i n k [ 5 ] = l i n k [ 3 ] = S link[5]=link[3]=S link[5]=link[3]=S , l i n k [ 4 ] = l i n k [ 3 ] = 5 link[4]=link[3]=5 link[4]=link[3]=5 .
总时间复杂度 O ( ∣ S ∣ ) O(|S|) O(∣S∣) ,具体证明见 后缀自动机详解_DZYO的博客 .
由上述构造可知状态规模大致在 O ( 2 × n − 1 ) O(2\times n -1) O(2×n−1) 范围, t r a n s trans trans 大致在 O ( 3 × n − 4 ) O(3\times n -4) O(3×n−4) 规模 .
Code
struct Suffix_automata
{
int maxlen[N] , minlen[N] , link[N] , trans[N][26];
int Siz , Las;
Suffix_automata() {Siz = Las = 1;}
inline void Extend(int id)
{
int cur = ++Siz , p;
maxlen[cur] = maxlen[Las] + 1;
for(p = Las ; p && !trans[p][id] ; p = link[p]) trans[p][id] = cur;
if(!p) link[cur] = 1;
else
{
int q = trans[p][id];
if(maxlen[q] == maxlen[p] + 1) link[cur] = q;
else
{
int y = ++Siz;
maxlen[y] = maxlen[p] + 1;
memcpy(trans[y] , trans[q] , sizeof trans[q]);
link[y] = link[q];
for(; p && trans[p][id] == q ; p = link[p]) trans[p][id] = y;
link[cur] = link[q] = y;
}
}
Las = cur;
ans += maxlen[cur] - maxlen[link[cur]];
}
} T;
应用
本质不同子串个数
实际上就是求所有 e n d p o s endpos endpos 的并,跳 Suffix-path \text{Suffix-path} Suffix-path 即可 .
任意子串出现次数
跳相当于查对应状态 e n d p o s endpos endpos 的大小,即 ∣ e n d p o s ( x ) ∣ |endpos(x)| ∣endpos(x)∣ .
对于状态 u u u ,沿着 Suffix-path ( u → S ) \text{Suffix-path}(u\rightarrow S) Suffix-path(u→S) 走到的状态都是 l ( u ) l(u) l(u) 的后缀,因此 ∣ e n d p o s ( u ) ∣ |endpos(u)| ∣endpos(u)∣ 可以贡献给其路径上经过的每一个点,均摊下来等价于找 Suffix-path ( u → S ) \text{Suffix-path}(u\rightarrow S) Suffix-path(u→S) 的起始端点 .
又由于 m a x l e n [ l i n k [ u ] ] < m i n l e n [ u ] maxlen[link[u]]<minlen[u] maxlen[link[u]]<minlen[u] ,因此沿着 l i n k link link 边走相当于走一个 DAG \text{DAG} DAG ,再由出边唯一,因此这是一棵树 .
起始端点就相当于这棵树的叶子,求一下即可 .
两个串的最长公共子串
还是增量构造 .
考虑将模式串的 SAM \text{SAM} SAM 建出来,设 T [ i ] T[i] T[i] 表示以模式串第 i i i 位为末尾的最长公共子串 .
以下面例子为例:
S:aabbabd
T:abbabb
1:a
2:ab
3:abb
4:abba
5:abbab
6: ab
我们发现往后加一个字符相当于跳 t r a n s trans trans .
记 u , l u,l u,l 分别为当前 T [ i ] T[i] T[i] 所在的状态以及它在原串中的长度 .
假设已知上一个 T [ i − 1 ] T[i-1] T[i−1] 的 u ′ u' u′ , l ′ l' l′ ,现在推 T [ i ] T[i] T[i] 的 u u u 和 l l l .
- t r a n s [ u ′ ] [ T [ i ] ] = v trans[u'][T[i]]=v trans[u′][T[i]]=v , v ≠ ∅ v\not=\empty v=∅ ,那 u = v u=v u=v , l = l ′ + 1 l=l'+1 l=l′+1 即可.
- t r a n s [ u ′ ] [ T [ i ] ] = v trans[u'][T[i]]=v trans[u′][T[i]]=v , v = ∅ v=\empty v=∅ ,那就跳 l i n k link link ,直到找到一个状态 u u uu uu 使得 t r a n s [ u u ] [ T [ i ] ] ≠ ∅ trans[uu][T[i]]\not= \empty trans[uu][T[i]]=∅ ,注意到此时有 l ( u u ) + T [ i ] = S ( T [ i ] ) l(uu)+T[i]=S(T[i]) l(uu)+T[i]=S(T[i]) ,因此 u = t r a n s [ u u ] [ T [ i ] ] u=trans[uu][T[i]] u=trans[uu][T[i]] , l = m a x l e n [ u u ] + 1 l=maxlen[uu]+1 l=maxlen[uu]+1 即可 . 不存在就直接 u = 0 u=0 u=0 , l = 0 l=0 l=0 .
总时间复杂度 ∣ S ∣ + ∣ T ∣ |S|+|T| ∣S∣+∣T∣ ,因为最多把 l i n k link link 和 t r a n s trans trans 全跳一遍 .
具体应用
P2178 [NOI2015] 品酒大会
一个朴素的想法是,将字符串倒置,然后建出 SAM \text{SAM} SAM .
考虑一个状态的 m i n l e n minlen minlen 和 m a x l e n maxlen maxlen 和 s i z siz siz ,这里的 s i z siz siz 表示 ∣ e n d p o s ∣ |endpos| ∣endpos∣ .
如果 s i z = 1 siz = 1 siz=1 ,那么很遗憾它的起点一样,说明拿不出两杯不同位置的酒(不过可以用来做 " 0 0 0 相似" 的情况).
否则这里边可以取出任意长度在 [ m i n l e n , m a x l e n ] [minlen\ , \ maxlen] [minlen , maxlen] 之间的酒,并且他俩起始酒不属于同一杯酒 .
于是问题转化成便利状态,将 e n d p o s endpos endpos 外挂上状态上,选两个最大的乘起来再线段树区间赋值即可,最后这步离线,因此也可以打标记单调队列 .
但这样会有个小问题是,挂美味度时可能会被卡到 O ( n 2 ) O(n^2) O(n2) .
于是将美味值挂在叶子上,转移时继承即可 .
实际写的时候,还可以写成 DAG \text{DAG} DAG 式的转移 .
P4248 [AHOI2013]差异
考虑意义,等价于反串 p a r e n t parent parent 树上两个节点路径之和,其中每条边的权值为 m a x l e n [ u ] − m a x l e n [ l i n k [ u ] ] maxlen[u]-maxlen[link[u]] maxlen[u]−maxlen[link[u]] .
拆掉,对单条边考虑贡献即可 .