您的位置:首页 > 其它

【学习笔记】后缀自动机(SAM)

2022-06-03 16:01 1371 查看

后缀自动机

可怜的我,学了两三遍才终于有点感觉了,也是因为这东西是真的复杂。

基本定义

后缀自动机(SAM)是用来解决字符串问题的一种数据结构,可以理解为字符串的压缩形式,是一个有向无环图。其中从源点出发到达每一个点的路径都是原串的一个子串,且原串的每一个子串都一定可以表示为一条路径。

endpos

定义

在字符串 s 中,我们记 endpos(t) 为字符串 t 在 s 中所有的结束位置。例如在字符串 "abcbc" 中 endpos("bc") = {3,5} 。

我们就将 endpos 值相同的字符串称作一个等价类,即在同一个等价类中必定存在 endpos(t_1) = endpos(t_2),其中 t_1,t_2 为两个字符串

SAM 中的每个节点对应一个等价类,即 SAM 节点的数量为等价类的数量加一,因为存在一个初始状态。

性质

**性质一:**若存在 endpos(t_1) = endpos(t_2),且满足 |t_1| \le |t_2| ,即在字符串 s 中, t_1 都是以 t_2 的后缀的形式出现。

**性质二:**对于每一个 endpos 等价类,等价类中的子串长度一定恰好完全覆盖 [l,r],l 为最短子串长度, r 为最长子串长度。且任取两个子串,较短者一定是较长者的后缀。

后缀链接(link)

定义

对于一个状态 t,我们记 w 为其的最长子串,那么一个后缀链接就是由 t 连向 w 的所有后缀中与 w 的 endpos 值不同且最长的子串的状态。(好绕啊)

性质

性质一: 所有后缀链接构成一棵以源点为根的树

(转自 OI_Wiki)

构造

SAM 的构造看上去很简单,但实际上非常难以理解。

(转自 OI_Wiki)

下面就是详细地解释一下这个东西。

SAM 的构造是在线的,也就是说我们是在原有的字符串末尾一点点插入字符然后构造的。

第一个点:没有什么好说的

第二个点:len(t) 即 t 这个节点代表的状态下的最长子串的长度,新加入的这个状态代表的最长子串也就是整个字符串,长度也就是没有这个点的时候的字符串长度加一

第三个点:其实也没啥说的,就是找到一个状态 p,使得 p 是从 last 沿后缀链接向前第一个遇到的有字符 c 的转移的状态,转移可以理解为从这个状态的最长子串后面加一个 c 能得到的子串所对应的状态。

第四个点:所有的能从 last 沿后缀链接能到的状态都没有字符 c 的转移,也就是原串里加入字符 c 之前,不含有字符 c,那么根据后缀链接的定义,连接任何状态都不对,所以只能与源点项链。所谓从 last 沿后缀链接能到的状态,可以理解为其的子串一定包含原串的所有后缀,但不仅包含这些而已。

第五个点:没啥说的,其实就是找到一个状态 q 使得 q 是 p 的最长子串后加入一个字符 c 得到的状态。

第六个点:略过

第七个点:根据后缀链接的定义,那么对于 p 中的最长子串一定是不加入字符串 c 的原串的后缀,那么其加入一个字符 c 之后就一定是新串的后缀,而 p 又是第一个找到的,也就是满足该条件的状态里最长字串最长的一个,那么在 p 后面加入一个字符 c,也就可以理解为新串的后缀,也就是 q 状态,而 q 一定不等于 cur,而 q 又是最长的一个满足条件的新串的后缀,也就是 cur 的后缀,满足后缀链接的定义,所以应该从 cur 后缀链接到 q。

第八个点:这里前提条件就是 q 含有比 p + c 更长的一个字符串,但是考虑一点 p+c 是新串的后缀,而 p+c 与 q 的 endpos 相同,也就是说 q 里面包含的这个最长的字符串也肯定是新串的后缀。下面我们就考虑分出来这样的一个点,也就是 clone ,使得 clone 是类似第七个点情况下的 q ,所以这样的话就应该使得 cur 连向 clone, q 连向 clone,根据后缀链接的定义,clone 肯定是 q 中的后缀,而且它们的 endpos 一定不同,所以这样链接。这样的话加上字符 c 的转移就有了更加好的去处,也就是 clone

第九个点:略过。

可能是相当的不严谨,但是我们也没必要有那么优秀的证明能力,能有自己理解的逻辑应该就够了。

时间复杂度: O(|S|)

用处:

(OI_Wiki 上的加上我的理解)

(1)检查一个字符串是否出现过:

因为原串的每一个子串都一定是从源点出发的一条路径,所以就从源点扫就好了

(2) 询问不同子串个数: 1.我们的 SAM 是一个有向无环图,因为每一条路径都对应原串的每一个子串,所以就相当于统计路径条数, DP 即可

2.考虑 SAM 本身的定义: SAM 上的节点定义为不同的 endpos 等价类,那么我们只要能求出所有等价类的对应的子串的数量然后求和即可。对于同一 endpos 等价类中的子串,其长度一定恰好完全覆盖 [len_{link[i]} + 1,len_i],因为我们的后缀链接存在一个性质,len[link[now]] = minlen[now] - 1,根据定义也非常好理解

(3)所有不同子串的总长度: 1.利用有向无环图, DP 就好了

(4)字符串第 k 大子串: 字符串第 k 大子串即 SAM 上的第 k 大路径,求出每个状态的路径条数,然后找就可以了

(5)最小循环移位: S + S 这个字符串里长度为 |S| 的一段,一定对应着 S 的一个循环进位,就对 S + S 建 SAM 然后贪心选择最小的长度为 |S| 的一段就好了

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: