(题解 P6139)SAM/广义SAM 与 AC 自动机 之间的关系

您所在的位置:网站首页 ac自动机应用 (题解 P6139)SAM/广义SAM 与 AC 自动机 之间的关系

(题解 P6139)SAM/广义SAM 与 AC 自动机 之间的关系

2024-06-10 11:00| 来源: 网络整理| 查看: 265

(题解 P6139)SAM/广义SAM 与 AC 自动机 之间的关系

warzone

2021-03-26 20:30:20

Solution AC 自动机应用熟练后,自己兴冲冲地去学 SAM 。 结果被劝退了,鸽了大半年才出来。 总结了下学习上的问题,发现网上的 blog 普遍有以下问题: - 定义繁琐,刚学时直接一个 $\operatorname{endpos}$ 糊你脸上,极其劝退。 - 一开始所谓压缩、合并解释突兀,无法循序渐进地推出结论。 因此就有了这篇文章。 前置芝士: - 自动机的定义: 字典树拓展为 “字典图” 就是自动机。如下就是一个合法的自动机: ![](https://cdn.luogu.com.cn/upload/image_hosting/9zk0zhon.png) 其中的结点称为**状态**,带权的有向边称为**转移**, 遍历的起点即**初始状态**,按照字符串走到的对应结点称为**目标状态**。 状态的数量既可以是有限的,也可以是无限的。 同一状态同一权值的出边既可以只有一个(**确定性**的), 也可以有多个(**非确定性**的)。 OI 一般只研究 有限状态确定性自动机(DFA)。 - [AC 自动机及其原理](https://www.luogu.com.cn/blog/wangrx/solution-p5357) ## 问题引入 > 给定一个字符串 $s$,求另外若干个字符串是否为 $s$ 的子串,强制在线。 对于这个问题,离线可以通过将 $s$ 以外的字符串建立 AC 自动机来解决。 但该问题要求在线,因此易想到建立一个关于 $s$ 的字典树,存储 $s$ 的所有子串。 如字符串 $\texttt{cabab}$ 子串构成的字典树如下: ![](https://cdn.luogu.com.cn/upload/image_hosting/rmp42as9.png) 但显然,这种做法时空复杂度均为 $\Theta(|s|^2)$ , 因此需要一种快速构建的方法,于是就有了 SAM。 ## 字符的加入与 fail 指针 字符串算法通常为 $\Theta(|s|)$,这启示我们考虑加入单个字符的情况。 考虑末尾加入一个字符 $\texttt{c}$,则显然,加入的子串有: ```plain cababc ababc babc abc bc c ``` 可以发现,它们都是由 $s$ 的后缀(可以为空)添加一个字符 $\texttt{c}$ 而来。 我们将 $s$ 的后缀在字典树上标记出来(包括空串): ![](https://cdn.luogu.com.cn/upload/image_hosting/haeq11h5.png) 这个时候,我们意识到:遍历 $s$ 的后缀的过程,不就是跳 AC 自动机的 fail 指针吗? 因为 fail 指针的定义是: > 字符串 $s$ 在字典树上出现了的最长真后缀(可以为空)。 于是我们标记上 $s$ 的后缀的 fail 指针,可知它们形成了一条链: ![](https://cdn.luogu.com.cn/upload/image_hosting/58at5gps.png) 于是只要顺着这条链往上跳,就可以遍历 $s$ 的所有后缀,添加结点了: ![](https://cdn.luogu.com.cn/upload/image_hosting/gc3fm95y.png) 同时,我们也可以知道新增结点的 fail 指针: 设状态 $u$ 边权为 $c$ 的出边为 $u\mathop{\rightarrow}\limits^c$,则在 AC 自动机上有: - $u\mathop{\rightarrow}\limits^c$ 为 $u$ 在字典树上的儿子,则 $fail_{u\mathop{\rightarrow}\limits^c}=fail_u\mathop{\rightarrow}\limits^c$(转移 1) - 否则,$u\mathop{\rightarrow}\limits^c=fail_u\mathop{\rightarrow}\limits^c$(转移 2) 这个过程并没有优化复杂度,但却给我们以极大的启发: 如果将多个状态压缩为一个,我们也可以通过维护 fail 指针来构建自动机。 ## 结束位置与状态压缩 考虑抠除 字典树上的出边,只保留 fail 指针(也就是只看 fail 树): ![](https://cdn.luogu.com.cn/upload/image_hosting/0xeaa0j8.png) 看不清?那就整理一下: ![](https://cdn.luogu.com.cn/upload/image_hosting/p7cxlu99.png) 可以看到,fail 树上存在“单独”(除链底外均没有儿子)的链, 比如 $4\rightarrow 8\rightarrow 11,5\rightarrow 9\rightarrow 12,7\rightarrow 10$ 压缩这些链,就能降低跳 fail 树的时空间成本(暂时先不管出边)。 回到整理前的图,考虑这些链的意义,发现这和子串在 $s$ 中的结束位置有关: - 对于子串 $a,b$, $a,b$ 存在一个相同的结束位置 $\Leftrightarrow$ fail 树上其中一个为另一个的祖先。 这是显然的,因为相同的结束位置意味着其中一个为另一个的后缀。 - 在该 fail 树上,子串 $a$ 为子串 $b$ 父亲 $\Leftrightarrow a$ 恰好为 $b$ 去掉第一个字符。 由 fail 指针定义可知 $a$ 为 $b$ 在字典树上的最长真后缀, 至多为 $b$ 去掉第一个字符。 由于字典树上存储了 $s$ 的所有子串,因此 $b$ 的所有子串也在里头, 这个可能的最大值必然存在。 由此引出了 $\operatorname{endpos}$ 的定义: $\operatorname{endpos}(t)$ 为子串 $t$ 在原串中所有结束位置构成的集合。于是可知: - 该 fail 树上,$a$ 为 $b$ 的祖先 $\Leftrightarrow a$ 为 $b$ 的后缀 $\Leftrightarrow\operatorname{endpos}(a)\supseteq \operatorname{endpos}(b)$ - 同一条“单独”的链上的子串 $\Leftrightarrow\operatorname{endpos}$ 相同。 - $\operatorname{endpos}$ 相同的子串,其长度是连续的,且**出边的 $\operatorname{endpos}$ 相同**。 因此,$\operatorname{endpos}$ 相同的结点就会被我们压缩为一个结点,其出边保持不变, 且可以用最长串和最短串的长度得出压缩的点的数量。 ## SAM 构建 说了这么多,终于到了 SAM 的构建。 除了出边,我们还要维护以下两个数组: - 后缀链接 $\operatorname{link}$:"单独"的链 链顶的 fail 指针。 - $\operatorname{len}$:压缩的链中最长串的长度。 以及一个额外变量: - $last$:整个串的末尾所在结点。 初始时,我们只有自动机的起点,即空串。 每加入一个字符 $c$ 至末尾,新增一个结点 $v,\operatorname{len}_v=\operatorname{len}_{last}+1$, 然后从 $last$ 往上跳后缀链接,设跳到的结点为 $u$,则: - 若 $u\mathop{\rightarrow}\limits^c$ 不存在,由 转移 2 可知,将 $u\mathop{\rightarrow}\limits^c$ 置为 $v$。 - 否则,由转移 1 可知,$\operatorname{link}_v$ 必在 $u\mathop{\rightarrow}\limits^c$ 中。 - 若 $\operatorname{len}_{u\mathop{\rightarrow}\limits^c}=\operatorname{len}_u+1$,则直接将 $\operatorname{link}_v$ 置为 $u\mathop{\rightarrow}\limits^c$,退出; - 否则,将 $u\mathop{\rightarrow}\limits^c$ 分裂为两个结点。 具体地,从 $u\mathop{\rightarrow}\limits^c$ 复制出一个结点, 旧的称为 $old$,新的称为 $new$,二者出边相同, 然后将 $\operatorname{len}_{new}$ 置为 $\operatorname{len}_u+1$。 接下来,将 $\operatorname{link}_{new}$ 置为 $\operatorname{link}_{old}$,再将 $\operatorname{link}_{old}$ 置为 $new$。 从 $u$ 继续跳后缀链接,若 $u\mathop{\rightarrow}\limits^c=old$,则置 $u\mathop{\rightarrow}\limits^c$ 为 $new$, 直到跳完了根或者 $u\mathop{\rightarrow}\limits^c\not=old$。 之后,将 $\operatorname{link}_v$ 置为 $u$,退出。 - 若跳完了根都未出现转移,则将 $\operatorname{link}_v$ 置为空串。 最后,记得将 $last$ 置为 $v$。 ```cpp typedef unsigned int word; struct SAM{ word next[1


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3