后缀自动机

定义

定义 SAM 为一个有限状态自动机,接受且仅接受 \(S\) 的一个后缀。

同时,SAM 是这样的自动机中最小的那个,其中状态数至多为 \(2n - 1\),转移数至多为 \(3n - 4\)。

基本性质

  1. SAM 是一张 DAG。
  2. SAM 上从源点 \(t_0\) 出发经过的任意一条路径为原串的一个子串,因此 SAM 上一个节点对应一个子串集合。

单有这些基础性质是不够的,我们可以考虑多寻找一些 SAM 的性质来使用必要条件构造出 SAM。

首先我们需要引入一些强相关定义:

结束位置 endpos

对于字符串 \(s\) 的任意非空子串 \(t\),我们记 \(\mathrm{endpos}(t)\) 为 \(t\) 在 \(s\) 中的所有出现的结尾位置构成的集合。

对于 \(\rm endpos\),我们不加证明地给出如下性质。

  • 对于 \(s\) 的任意两个非空子串 \(t_1, t_2(|t_1| \le |t_2|)\),其中 \(t_1\) 为 \(t_2\) 的一个后缀当且仅当:\(\mathrm{endpos}(t_2) \subseteq \mathrm{endpos}(t_1)\)。

  • 对于 \(s\) 的任意两个非空子串 \(t_1, t_2(|t_1| \le |t_2|)\),一定满足:\(\mathrm{endpos}(t_1) \cap \mathrm{endpos}(t_2) = \mathrm{endpos}(t_2) / \varnothing\)。

在给出第三条性质之前,我们先定义 \(\rm endpos\) 集等价类,即我们将 \(\rm endpos\) 集相同的子串看作是一个等价类。

  • 对于同一等价类内的子串,其长度连续。

有了关于 \(\rm endpos\) 集的性质之后,我们发现对于 SAM,需要满足如下性质:

  • SAM 上任意一个节点对应子串的 \(\rm endpos\) 集相同。

对于任意的节点 \(x\),我们考虑其表示的任意一个子串 \(t\) 到经过其的后缀的结尾距离构成的集合 \(S\)。

不难发现就是从 \(x\) 开始往后遍历到所有终止态经过的节点数构成的集合。

该集合的构成显然与 \(t\) 无关,仅与 \(x\) 有关因此该性质成立。

  • SAM 上不存在两个节点 \(u, v\) 使得 \(u, v\) 的 \(\rm endpos\) 集相同。

反证法:若存在则可以将两个节点合并(转移也合并),SAM 减小与最小性不符。

由此,我们可知 SAM 上的每个节点唯一对应原串的一个等价类。

后缀 Link

对于字符串 \(s\) 的任意一个子串 \(t\),定义后缀 \(\mathrm{link}_t\) 为 \(t\) 最长的后缀使得其与 \(t\) 的 \(\rm endpos\) 集不同,特别地,若不存在则将 \(\mathrm{link}_t \leftarrow 0\)。

容易得知,根据后缀 \(\rm link\) 连边,我们可以得到一颗树,这颗树称为 \(\rm parent\) 树。

但这个信息量是巨大的,我们不妨使用 SAM 的结构和 \(\rm endpos\) 的性质压缩这个结构。

  • 根据 \(\rm endpos\) 集的定义,同一个等价类中子串的后缀 \(\rm link\) 是相同的。

因此我们可以将所有子串压缩为等价类一个节点,而将连边改为等价类之间的连边,从直观上来看,这样大大减少了连边数。

压缩之后为了储存信息,根据等价类的性质 \(3\),我们只需储存当前等价类内子串的最长长度 \(len\),和最短长度 \(minlen\) 即可。

同时,后缀 \(\rm link\) 还满足如下性质:

  • 对于字符串 \(s\) 的任意子串 \(t_1\),对于另一个子串 \(t_2\),其中 \(t_2\) 为 \(t_1\) 在 \(\rm parent\) 树上的祖先当且仅当 \(t_2\) 为 \(t_1\) 的一个后缀。

  • \(\rm parent\) 树的节点数至多为 \(2n - 1\),即等价类 / SAM 上的节点数至多为 \(2n - 1\)。

考虑从上往下暴力构造一颗 \(\rm parent\) 树。

具体地,我们从根节点 \(t_0\) 出发,特别地,我们令此时的 \(\rm endpos\) 集为全集,同时维护一个字符串,初始时为空串。

每次我们在当前的的字符串前加上一个字符 \(c\),计算该字符串的 \(\rm endpos\) 集。

我们扣掉 \(\rm endpos\) 集为空集的字符串,若剩下的字符串 \(\rm endpos\) 集只存在一种,那么显然这些字符串与当前字符串的 \(\rm endpos\) 集相等,可以直接将这些字符串归为同一个等价类,此时节点数不变。

若剩下的字符串存在至少两种 \(\rm endpos\) 集,那么按照 \(\rm endpos\) 集的不同进行划分新建节点将这些节点连向当前节点。对这些新的节点递归操作。

我们只关心新建的节点总数量,可以发现这个流程可以抽象为将一个大小为 \(n\) 的集合不断划分至大小为 \(1\) 的集合,划分的次数即为新建的节点数量。

对于这个划分数量的最大值可以简单地得到一个 \(2n - 2\) 的上界,加上源点总共至多 \(2n - 1\) 个节点。

SAM 的构造

一般地,考虑增量法。令当前字符串为 \(s\),位置为 \(i\),需要往后加一个字符 \(c\)。

首先对于 \(s + c\) 的后缀,\(\rm endpos\) 集一定存在 \(i + 1\),且之前的所有 \(\rm endpos\) 都不存在 \(i + 1\),因此一定要新建节点 \(new\)。

为了求得 \(s + c\) 的所有后缀,我们可以遍历 \(s\) 的所有后缀然后在其后添加字符 \(c\)。

由此,我们可以借助 \(\rm parent\) 树来遍历 \(s\) 的所有后缀,同时,我们也需要维护出新建节点的后缀 \(\rm link\)。

此时我们发现,对于 \(s\) 的后缀,一定满足:

  • 存在一个分界点使得前一部分后缀满足 \(+c\) 后在 \(s\) 中不出现,后一部分后缀满足 \(+c\) 后在 \(s\) 出现。同时该分界点在两个等价类之间。

因此对于分界点以前的节点,显然 \(+c\) 后 \(\rm endpos\) 集中只存在 \(i + 1\),因此可以直接在 SAM 上向 \(new\) 连边。

特别地,若不存在一个后缀满足 \(+c\) 后在 \(s\) 中出现,我们将 \(\mathrm{link}_{new} \leftarrow 0\),接下来将不会考虑这种情况。

我们找到第一个等价类使得其 \(+c\) 后在 \(s\) 中出现,令该节点为 \(p\),找到 \(+c\) 的串在原 \(s\) 当中的等价类 \(q\)。

此时我们有如下性质:

  • \(len_q \ge len_p + 1, minlen_q \le minlen_p + 1\)。

我们可以发现,此时 \(q\) 这个等价类当中长度为 \((len_p + 1, len_q]\) 的子串 \(\rm endpos\) 集中将不存在 \(i + 1\),而长度为 \([minlen_q, len_p + 1]\) 的子串 \(\rm endpos\) 集中包含 \(i + 1\)。

(特别地,若 \(len_q = len_p + 1\) 我们最后考虑)

由此,我们需要将 \(q\) 分裂为两个等价类,我们新建节点 \(clo\),令它表示后者构成的等价类,那么需执行如下操作:

先将 \(q\) 的全部信息复制给 \(clo\),然后执行如下操作:len[clo] = len[p] + 1, link[q] = clo

由于等价类已经改变,因此之前连向 \(q\) 的转移需要重新定向。

显然需要重新定向的只有 \(p\) 的祖先节点当中有向 \(q\) 转移的节点,需要把它们都改为 \(clo\)。

同时,我们需要把 \(new\) 的后缀 \(\rm link\) 指向 \(clo\)。

最后,如果 \(len_q = len_p + 1\),那么 \(q\) 中所有的子串 \(\rm endpos\) 都会加上 \(i + 1\),我们只需要修改 \(new\) 的后缀 \(\rm link\) 指向 \(clo\)。

正确性证明

容易发现,除了最小性以外,开始提到的 SAM 需要满足的所有性质都能很容易地利用构造 SAM 的流程证明正确性。

而最小性的证明理论比较复杂,这里不涉及。

复杂度证明

状态数

在后缀 \(\rm link\) 中我们已经使用了一种与 SAM 构造无关的方式证明了一个上界为 \(2n - 1\)。

同时,通过 SAM 的构造,我们可以轻松地看出 SAM 的状态数也即等价类数量的一个上界为 \(2n - 1\)。

转移数

我们令 \(last\) 表示考虑完 \(s\) 构造的 SAM 中 \(s\) 这个整串对应的状态。

我们发现,每次向 \(new\) 连边,\(last\) 会一直跳后缀 \(link\),最后停下的位置为 \(top\) 那么其一定满足:

  • \(minlen_{clo} \le minlen_{top} + 1\)。

令 \(top\) 由 \(lst\) 这个位置跳过来。

  • \(minlen_{new} \le minlen_{lst} + 1\)

考虑令势能函数 \(\varphi(x) = minlen_x\)。

\[\varphi(last) - \varphi(lst) + 1 \le \varphi(last) - \varphi(new) + 2
\]

于是总连边量(不包括复制连边)为:

\[\sum \varphi(last) - \varphi(new) + 2 = 2n - \varphi(new_n) \le 2n - 1
\]

复制连边的复杂度证明好像假了,先鸽。。。先记着总转移数大小的结论:不超过 \(3n - 4\).

复杂度

首先,除了重定向部分,其余部分的复杂度与状态数和转移数相同,因此只考虑重定向部分的复杂度。

令势能函数 \(\lambda(x) = \mathrm{len}(\mathrm{link}(\mathrm{link}(x)))\),那么重定向的复杂度有上界:\(\varphi(top) - \lambda(new) \le \varphi(last) - \lambda(new)\).

更近一步地,将 \(last\) 初次跳两次后缀 \(\rm link\) 的复杂度单独写开:\(2 + \lambda(last) - \lambda(new)\) 那么有总复杂度:

\[\sum\limits 2 + \lambda(last) - \lambda(new) \le 2n
\]

因此构建 SAM 的复杂度是线性的。

广义后缀自动机

定义

广义后缀自动机是一个 DFA 接受且仅接受给定的多个模式串 \(s_1, s_2, \cdots s_m\) 其中任意一个的任意后缀。

同样的,广义后缀自动机也满足最小性,核心构造思路(\(endpos\) 集等价类合并压缩)与 SAM 完全一致,性质为 SAM 扩展到多模式串层面,类似于 KMP 自动机和 AC 自动机之间的关系。

构造方式

首先类似于 AC 自动机的构建方式,我们将给定的 \(m\) 个模式串建出 trie 树。

然后按照 bfs 序依次建立,这时与 SAM 建立时满足的性质完全相同。

复杂度

状态数和转移数上界与 SAM 中相同,只与 trie 树大小相关。

重定向部分利用与 SAM 重定向部分一样的势能分析方法,复杂度也是线性的。

后缀自动机 (SAM)的更多相关文章

  1. [转]后缀自动机(SAM)

    原文地址:http://blog.sina.com.cn/s/blog_8fcd775901019mi4.html 感觉自己看这个终于觉得能看懂了!也能感受到后缀自动机究竟是一种怎样进行的数据结构了. ...

  2. 【算法】后缀自动机(SAM) 初探

    [自动机] 有限状态自动机的功能是识别字符串,自动机A能识别字符串S,就记为$A(S)$=true,否则$A(S)$=false. 自动机由$alpha$(字符集),$state$(状态集合),$in ...

  3. SPOJ 1811. Longest Common Substring (LCS,两个字符串的最长公共子串, 后缀自动机SAM)

    1811. Longest Common Substring Problem code: LCS A string is finite sequence of characters over a no ...

  4. 后缀自动机SAM学习笔记

    前言(2019.1.6) 已经是二周目了呢... 之前还是有一些东西没有理解到位 重新写一下吧 后缀自动机的一些基本概念 参考资料和例子 from hihocoder DZYO神仙翻译的神仙论文 简而 ...

  5. 浅谈后缀自动机SAM

    一下是蒟蒻的个人想法,并不很严谨,仅供参考,如有缺误,敬请提出 参考资料: 陈立杰原版课件 litble 某大神 某大神 其实课件讲得最详实了 有限状态自动机 我们要学后缀自动机,我们先来了解一下自动 ...

  6. 后缀自动机(SAM)奶妈式教程

    后缀自动机(SAM) 为了方便,我们做出如下约定: "后缀自动机" (Suffix Automaton) 在后文中简称为 SAM . 记 \(|S|\) 为字符串 \(S\) 的长 ...

  7. 【算法】后缀自动机(SAM) 例题

    算法介绍见:http://www.cnblogs.com/Sakits/p/8232402.html 广义SAM资料:https://www.cnblogs.com/phile/p/4511571.h ...

  8. 后缀自动机(SAM)速成手册!

    正好写这个博客和我的某个别的需求重合了...我就来讲一讲SAM啦qwq 后缀自动机,也就是SAM,是一种极其有用的处理字符串的数据结构,可以用于处理几乎任何有关于子串的问题,但以学起来异常困难著称(在 ...

  9. 【算法专题】后缀自动机SAM

    后缀自动机是用于识别子串的自动机. 学习推荐:陈立杰讲稿,本文记录重点部分和感性理解(论文语言比较严格). 刷题推荐:[后缀自动机初探],题目都来自BZOJ. [Right集合] 后缀自动机真正优于后 ...

  10. 【文文殿下】对后缀自动机(SAM)的理解

    后缀自动机,是一种数据结构,是由状态和转移关系构成的.它虽然叫做后缀自动机,可是他却与后缀并没有什么太大的联系. 后缀自动机的每一种状态都是原串的一些子串的集合,每个子串只唯一存在于某个状态中,对每一 ...

随机推荐

  1. POJ 3278:The merchant(LCA&DP)

    The merchant Time Limit: 3000MS   Memory Limit: 65536K Total Submissions: 6864   Accepted: 2375 Desc ...

  2. 第六个知识点:我们怎么把NP问题解释成一组可以在多项式内证明的命题

    第六个知识点:我们怎么把NP问题解释成一组可以在多项式内证明的命题 原文地址:http://bristolcrypto.blogspot.com/2014/11/52-things-number-6- ...

  3. 浏览器Cookie无法删除问题

    1.写Cookie时添加路径: //写cookies,设置过期时间为30天,并设置path为根目录 function setPathCookie(name,value) { var Days = 30 ...

  4. electron串口通信使用serialport安装报错

    1.报错信息没有安装python环境 1 gyp ERR! find Python 2 gyp ERR! find Python Python is not set from command line ...

  5. 新环境chart包helmlint校验

    在iot目录内可以执行helm lint iot-api 去校验

  6. jsp文件中文乱码解决

    文件顶加上 <%@ page contentType="text/html;charset=UTF-8" language="java" %>即可

  7. Git创建分支进行开发

    一.业务场景 自己当前开发的项目算是一个中型项目,整个项目都是由自己一个人开发完成,主要有两个子项目,一个是小程序的后台,一个是小程序的后台管理系统. 因为从一开始就只有我一个人在进行开发,所以自己平 ...

  8. Kube-OVN1.5.0新版本发布,支持鲲鹏云平台网络平面部署

    近日,Kube-OVN发布了最新的1.5.0版本.自2019年4月开源以来,Kube-OVN经历了15次重要版本迭代,以及社区成立,建设者贡献代码,稳定性测试,国内外用户开始在生产环境中投入使用,企业 ...

  9. 《剑指offer》面试题17. 打印从1到最大的n位数

    问题描述 输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数.比如输入 3,则打印出 1.2.3 一直到最大的 3 位数 999. 示例 1: 输入: n = 1 输出: [1,2,3,4,5 ...

  10. preg_match绕过总结

    preg_match绕过总结 什么是preg_match 绕过方法 1.数组绕过 preg_match只能处理字符串,当传入的subject是数组时会返回false 2.PCRE回溯次数限制 PHP利 ...