SAM 感性瞎扯
这里是 SAM 感性瞎扯。
最近学了后缀自动机(Suffix_Automaton,SAM),深感其巧妙之处,故写文以记之。
部分文字与图片来源于 OI-Wiki,hihoCoder 与一些个人博客,链接在文章最底端。
在此之前请先了解有关于 SAM 的所有函数,并最好理解 OI-Wiki 上有关于 SAM 的五条引理(链接在最底部,引理在下文中有提到)。
一些重要的定义与引理:
- \(T\):初始状态。
- \(\mathrm{endpos}(i)\):字符串 \(i\) 在 \(s\) 中所有出现的结束位置集合。例如当 \(s=\texttt{"abcab"}\) 时,\(\mathrm{endpos}(\texttt{"ab"})=\{2,5\}\),因为 \(s[1:2]=s[4:5]=\texttt{"ab"}\)。
- \(\mathrm{substr}(i)\):状态(SAM 上的一个节点)\(i\) 所表示的所有子串的集合。
- \(\mathrm{shortest}(i)\):状态 \(i\) 所表示的所有子串中,长度最短的那一个子串。
- \(\mathrm{longest}(i)\):状态 \(i\) 所表示的所有子串中,长度最长的那一个子串。
- \(\mathrm{minlen}(i)\):状态 \(i\) 所表示的所有子串中,长度最短的那一个子串的长度。即 \(\mathrm{minlen}(i)=|\mathrm{shortest}(i)|\)。
- \(\mathrm{len}(i)\):状态 \(i\) 所表示的所有子串中,长度最长的那一个子串的长度。即 \(\mathrm{len}(i)=|\mathrm{longest}(i)|\)。
- \(\mathrm{link}(i)\):\(\mathrm{longest}(i)\) 最长的一个后缀 \(w\ (w\notin \mathrm{substr}(i))\) 所在的状态。换句话说,一个后缀链接 \(\mathrm{link}(i)\) 连接到对应于 \(\mathrm{longest}(i)\) 的最长后缀的另一个 \(\mathrm{endpos}\) 等价类的状态。有 \(\mathrm{minlen}(i)=\mathrm{len(link}(i))+1\)。
引理 1: 字符串 \(s\) 的两个非空子串 \(u\) 和 \(w\)(假设 \(|u|\leq |w|\))的 \(\mathrm{endpos}\) 相同,当且仅当字符串 \(u\) 在 \(s\) 中的每次出现,都以 \(w\) 后缀的形式存在。证明详见 OI-Wiki,下(引理 2~5)同。
引理 2:考虑两个非空子串 \(u\) 和 \(w\)(假设 \(|u|\leq |w|\))。要么 \(\mathrm{endpos}(u)\cup \mathrm{endpos}(w)=\varnothing\),要么 \(\mathrm{endpos}(u)\subseteq\mathrm{endpos}(w)\),取决于 \(u\) 是否为 \(w\) 的一个后缀:
\[\begin{cases}\mathrm{endpos}(u)\subseteq\mathrm{endpos}(w)\quad &\mathrm{if}\ u\ \mathrm{is\ a\ suffix\ of}\ w\\\mathrm{endpos}(u)\cup \mathrm{endpos}(w)=\varnothing&\mathrm{otherwise}\end{cases}
\]
引理 3:考虑一个 \(\mathrm{endpos}\) 等价类,将其中所有子串按长度非递增的顺序排序。每个子串都不会比它前一个子串长,与此同时每个子串也是它前一个子串的后缀。换句话说,对于同一等价类的任一两子串,较短者总为为较长者的后缀,且该等价类中的子串长度恰好覆盖整个区间 \([\mathrm{minlen,len}]\)(即排序后长度连续且不等)。
引理 4:所有后缀链接构成一棵根节点为 \(T\) 的树。
- 定义后缀路径 \(p\to q\) 表示在后缀链接构成的树中 \(p\to q\) 的路径。
引理 5:通过 \(\mathrm{endpos}\) 集合构造的树(每个子节点的 \(subset\) 都包含在父节点的 \(subset\) 中)与通过后缀链接 \(\mathrm{link}\) 构造的树相同。
(图片来源于 OI-Wiki)
引理 6:对于一个状态 \(t\),\(\mathrm{substr}(t)\) 中所有子串后面接上同一个字符 \(c\) 之后,新的子串仍然都属于同一个状态(如果该状态存在)。
假设存在两个字符串 \(s_p,s_{p'}\in \mathrm{substr}(t)\) 满足 \(\mathrm{endpos}(s_p+c)\neq \mathrm{endpos}(s_{p'}+c)\)。不妨设 \(\mathrm{endpos}(s_{p'}+c)\) 包含 \(\mathrm{endpos}(s_p+c)\) 所没有的一个位置 \(pos\),那么 \(\mathrm{endpos}(s_{p'})\) 中必定含有 \(pos-1\),而 \(\mathrm{endpos}(s_p)\) 中必定没有。但是它们属于同一个状态,矛盾。得证。
一些重要的结论:(个人证明,并不非常严谨!)
结论 0:如果我们从任意状态 \(t_0\) 开始顺着后缀链接遍历,总会到达初始状态 \(T\)。这种情况下我们可以得到一个互不相交的区间 \([\mathrm{minlen}(t_i),\mathrm{len}(t_i)]\) 的序列,且它们的并集形成了连续的区间 \([0,\mathrm{len}(t_0)]\)。
该引理为 OI-Wiki 在 “小结” 最后一条列出的性质,根据后缀链接的性质和引理 3,4 易证。
结论 1:从表示 \(s[1:i]\) 的状态 \(t_0\) 不断跳后缀链接 \(\mathrm{link}\) 直到初始节点 \(T\),其遍历到的所有状态 \(t_0,t_1,t_2,\cdots,T\) 所包含的子串集合刚好为所有 \(s[1:i]\) 的后缀所表示的状态,即 \(\mathrm{substr}(t_0)\cup\mathrm{substr}(t_1)\cup\mathrm{substr}(t_2)\cup\cdots\cup\mathrm{substr}(T)=\{s[x:i]\ (1\leq x\leq i)\}\)。换句话说,如果你在 \(\mathrm{SAM}_i\) 上跑所有 \(s[1:i]\) 的后缀,那么跑到的所有状态都在后缀路径 \(t_0\to T\) 上。
证明 1:由后缀链接 \(\mathrm{link}\) 的定义,对于任意一个状态 \(t_0\),都有 \(x\) 是 \(y\) 的真后缀,其中 \(x\in\mathrm{substr(link}(t_0)),y\in\mathrm{substr}(t_0)\)。那么 \(S=\mathrm{substr}(t_0)\cup\mathrm{substr}(t_1)\cup\cdots\cup\mathrm{substr}(T)\) 中的所有子串(这里的子串是相对于 \(s[1:i]\) 而不是所包含的字符串的子串)都是 \(\mathrm{longest(t_0)}\) 即 \(s[1:i]\) 的后缀。又根据结论 0,\(S\) 中所有字符串长度覆盖了区间 \([0,\mathrm{len}(t_0)]\),即 \([0,i]\)。得证。
结论 2:一个状态 \(t\) 所表示的所有子串 \(\mathrm{substr}(t)\),等于初始状态 \(T\) 到该状态上所有路径所形成的所有字符串。如果添加一条转移边 \((p,q)\),字符为 \(c\),那么相当于将 \(\mathrm{substr}(p)\) 中所有子串末尾加上字符 \(c\) 添加到 \(\mathrm{substr}(q)\) 里面。
可以结合引理 6 与结论 1 感性理解(其实是我不太会证明,但是它满足这个性质)。如果想要直观理解可以看下文 Case 2 中的插图。
结论 3:考虑存在一个转移 \((p,q)\) 且 \(\mathrm{len}(p)+1=\mathrm{len}(q)\),那么其他所有指向 \(q\) 的转移 \((p_i,q)\) 的 \(p_i\) 都在后缀路径 \(p\to T\) 上。
证明 3:首先,根据结论 2,我们知道如果存在转移 \((p,q)\),字符为 \(c\),那么一定有 \(\mathrm{len}(p)+1\leq\mathrm{len}(q)\)。因此,假设有另外一个状态 \(p'\) 存在转移 \((p',q)\) 且 \(p'\) 不在后缀路径 \(p\to T\) 上(显然 \(p'\) 不能为 \(p\)),那么有 \(\mathrm{len}(p')< \mathrm{len}(p)\)。设 \(s_{p}=\mathrm{longest}(p),s_{p'}=\mathrm{longest}(p')\)。因为 \(s_p+c\) 与 \(s_{p'}+c\) 同属于 \(\mathrm{substr}(q)\),且 \(|s_{p'}+c|<|s_p+c|\),所以根据引理 3,\(s_{p'}+c\) 是 \(s_p+c\) 的后缀。所以 \(s_{p'}\) 是 \(s_p\) 的后缀。因此,根据引理 2,\(\mathrm{endpos}(s_p)\subsetneq \mathrm{endpos}(s_{p'})\ (p\neq p')\)。因此,根据引理 5,\(p'\) 在 \(\mathrm{link}\) 树上一定为 \(p\) 的父节点。这与假设矛盾。得证。
结合上图以更好理解(图片来源于 hihoCoder)。
推论 3:指向状态 \(q\) 的转移 \((p_i,q)\) 的所有状态 \(p_i\),在 \(\mathrm{link}\) 树上一定为一条深度递减的链 \(p_0\to p_x\),且有 \(\mathrm{minlen}(p_x)+1=\mathrm{minlen}(q),\mathrm{len}(p_0)+1=\mathrm{len}(q)\)。
SAM 的构造方式:
类似数学归纳法:假设已经构造好了 \(\mathrm{SAM}_{i-1}\),其状态为 \(las\),这样所需要做的就是添加一个字符 \(s_i\)。
首先,\(\mathrm{SAM}_{i-1}\) 是无法表示 \(s[1:i]\) 的(因为它只接受 \(s[1:i-1]\) 的子串),所以我们新建一个节点 \(cur\) 表示至少包含 \(s[1:i]\) 的状态,显然有 \(\mathrm{len}(cur)=i\)(或者说等于 \(\mathrm{len}(las)+1\))。可以发现 \(\mathrm{endpos}(s[1:i])=\{i\}\),因为 \(s[1:i]\in\mathrm{substr}(cur)\),而 \(s[1:i]\) 在 \(s[1:i]\) 中显然只以 \(i\) 为结束位置出现。
- 为什么说至少包含?因为 \(s[1:i]\) 的其它后缀也可能只以 \(i\) 为结束位置出现。例如 \(s=\texttt{aab}\)(假设 \(i=3\)),那么不仅是 \(\mathrm{endpos}(\texttt{"aab"})=\{3\}\),\(\texttt{"ab"}\) 与 \(\texttt{"b"}\) 的 \(\mathrm{endpos}\) 集合也为 \(\{3\}\)。
Case 1:根据引理 1 与 2,如果不考虑重复状态,那我们只需要将后缀路径 \(las\to T\) 上的所有状态往 \(cur\) 连一条字符为 \(s_i\) 的转移边,并将 \(\mathrm{link}(cur)\) 设为 \(T\) 即可(因为后缀路径 \(las\to T\) 上的所有点刚好表示了 \(s[1:i-1]\) 的所有后缀,在其后面添加字符 \(s_i\) 就可以表示 \(s[1:i]\) 的所有后缀)。这其实是 Case 1,即后缀路径 \(las\to t\) 上所有状态都没有字符 \(s_i\) 的转移边。容易发现这种情况仅在 \(s_i\) 未在 \(s[1:i-1]\) 中出现过时发生。
如果有重复怎么办?我们从 \(las\) 开始跳到的不重复的所有状态往 \(cur\) 连一条 \(s_i\) 的转移边,并设遇到的第一个重复的转移为 \((p,q)\)。为什么不能再添加 \((p,cur)\) 的 \(s_i\) 的转移了?因为这样会导致从 \(T\to q\) 和 \(T\to cur\) 可以表示相同的子串,破坏了 SAM 的性质。记在后缀路径 \(las\to T\) 跳到 \(p\) 的上一个状态为 \(p'\)(即 \(\mathrm{link}(p')=p\))。显然 \((p',cur)\) 有一条字符为 \(s_i\) 的转移。
此时 \(cur\) 的后缀链接应该怎么连?我们想要满足 \(\mathrm{len(link(}cur))+1=\mathrm{minlen}(cur)\)。这个 \(\mathrm{minlen}(cur)\) 实际上就是 \(\mathrm{minlen}(p')+1\),那么 \(\mathrm{len}(p)=\mathrm{minlen}(p')-1=\mathrm{len(link(}cur))-1\)(因为 \(\mathrm{link}(p')=p\))。
Case 2:\(\mathrm{len}(q)=\mathrm{len}(p)+1\),此时 \(q\) 中包含的最长子串就是 \(p\) 中的最长子串接上字符 \(s_i\)。那么只需将 \(\mathrm{link}(cur)\gets q\) 即可。因为 \(\mathrm{len}(q)=\mathrm{len}(p)+1=\mathrm{len(link(}cur))\),刚好是我们想要的。
(图片来源于 hihocoder)
上图中,在添加 \(s_5=\texttt{a}\) 时,从 \(T(S)\to 1\) 已经有了现成的字符 \(s_5\) 的转移。此时 \(p=T,q=1\)。因为 \(\mathrm{len}(1)=\mathrm{len}(T)+1\ (1=0+1)\),所以直接将 \(\mathrm{link}(6)\gets 1\) 即可。
注意上图中状态 \(4,5,6\) 所表示的子串,可以发现状态 \(6\) 所表示的子串是状态 \(4,5\) 所表示的所有子串后接上字符 \(s_5\) 得到的。这很好地验证了结论 2,也非常直观形象地阐释了 Case 2 的情况。
Case 3:\(\mathrm{len}(q)>\mathrm{len}(p)+1\),此时 \(q\) 对应了 \(s[1:i-1]\) 的更长的子串(感觉 OI-Wiki 这里讲得不是很明白)。此时除了把 \(q\) 拆开来别无他法。具体来说,将 \(q\) 所表示的所有长度不大于 \(\mathrm{len}(p)+1\) 的子串提出来,丢给一个新建的状态 \(q'\),然后将 \(\mathrm{link(cur)}\gets q'\),同时添加转移 \((q',cur)\),也就是说我们凭空创造了一个满足 Case 2 的状态 \(q\)。
显然,无中生有是要付出一些时间代价的。先考虑 \(q\) 和 \(q'\) 的内部情况。具体的,\(\mathrm{link}(q)\) 应改为 \(q'\),而 \(\mathrm{link}(q')\) 应该继承原来的 \(\mathrm{link}(q)\)。此外,一些本来转移到 \(q\) 的转移 \((p_i,q)\) 也应该变为 \((p_i,q')\)。具体地,我们从后缀路径 \(p\to T\) 继续往上跳,沿路径跳到的所有状态 \(p_i\),如果有一条 \((p_i,q)\) 的转移,那么将其改为 \((p_i,q')\)。
(图片来源于 hihocoder)
特别的,如果跳到一个状态 \(P\),它没有 \((P,q)\) 的转移,此时退出即可,因为根据结论 3,此时再往上跳也不可能出现另外一个状态 \(P'\) 有 \((P',q)\) 的转移。换句话说,我们找到后缀路径 \(p\to T\) 上从 \(p\) 开始的一段有 \((p_i,q)\) 转移的所有状态,并将其修改为 \((p_i,q')\)。因为根据推论 3,路径上有且仅有一段状态 \(p_i\) 有 \((p_i,q)\) 的转移,且所有 \(p_i\) 所表示的子串加上字符 \(s_i\),刚好能表示状态 \(q\) 原本所表示的所有长度不大于 \(\mathrm{len(p)}+1\) 的子串,如上图。
(图片来源于 hihocoder)
上图中,我们把 \(q=3\) 的不大于 \(\mathrm{len}(T)+1=1\) 的所有子串提出来,丢给一个新建的状态 \(q'=5\),然后 \(\mathrm{link}(4\ (cur))\gets 5\ (q')\) 并添加状态 \((5,4)\)(即 \((q',cur)\))。内部,\(\mathrm{link}(3\ (q))\gets 5\ (q')\),同时 \(\mathrm{link}(5\ (q')) \gets T\)(原来的 \(\mathrm{link}(3)\))。最后从 \(T\ (p)\) 往上跳后缀连接直到不存在连向 \(3\) 的路径或到了初始状态 \(T\)(当然,这里的例子只有 \(T\) 一个点,不过我们需要知道并不一定会跳到 \(T\),因为有可能跳到中间的某个状态 \(P\) 时就没有转移 \((P,3\ (q))\) 了),并将所有连向 \(3\ (q)\) 的转移连向 \(5\ (q')\),即 \((T,3)\) 变为了 \((T,5)\)。
综合上述三种情况,我们就可以在线性时间内建造出一个字符串 \(s\) 的 SAM(关于其时间复杂度为线性的证明,详见 OI-Wiki)。
部分额外信息在代码与应用中有提到。
代码与应用:
请移步 SAM 做题笔记。
一些资料:
如发现错误或有不理解的地方可以在下方评论区留言,我会尽快修改。
SAM 感性瞎扯的更多相关文章
- SAM 做题笔记(各种技巧,持续更新,SA)
SAM 感性瞎扯. 这里是 SAM 做题笔记. 本来是在一篇随笔里面,然后 Latex 太多加载不过来就分成了两篇. 标 * 的是推荐一做的题目. trick 是我总结的技巧. I. P3804 [模 ...
- 【算法专题】后缀自动机SAM
后缀自动机是用于识别子串的自动机. 学习推荐:陈立杰讲稿,本文记录重点部分和感性理解(论文语言比较严格). 刷题推荐:[后缀自动机初探],题目都来自BZOJ. [Right集合] 后缀自动机真正优于后 ...
- 后缀自动机SAM
某神犇:"初三还不会后缀自动机,那就退役吧!" 听到这句话后,我的内心是崩溃的. 我还年轻,我还不想退役--于是,我在后来,努力地学习后缀自动机. 终于,赶在初三开学前,我终于学会 ...
- SAM[详细~bushi]
基础性质概念 后缀自动机:S的SAM是个DAG,每个节点叫状态,每条带字符ch边表示+ch转移,从开始节点往下,任何一条路径都会对应一个S的子串. 不过为什么要叫"后缀"自动机呢? ...
- SAM复杂度证明
关于$SAM$的复杂度证明(大部分是对博客的我自己的理解和看法) 这部分是我的回忆,可省略 先回忆一下$SAM$ 我所理解的$SAM$,首先扒一张图 初始串$aabbabd$ 首先发现,下图里的$S- ...
- 后缀自动机(SAM)+广义后缀自动机(GSA)
经过一顿操作之后竟然疑似没退役0 0 你是XCPC选手吗?我觉得我是! 稍微补一点之前丢给队友的知识吧,除了数论以外都可以看看,为Dhaka和新队伍做点准备... 不错的零基础教程见 IO WIKI ...
- SAM初探
SAM,即Suffix Automaton,后缀自动机. 关于字符串有很多玩法,有很多算法都是围绕字符串展开的.为什么?我的理解是:相较于数字组成的序列,字母组成的序列中每个单位上元素的个数是有限的. ...
- bzoj4199:NOI2015D2T2品酒大会(SAM版)
SAM感觉写起来比SA更直观(?) #include <iostream> #include <cstdio> #include <cstring> #includ ...
- SAM/BAM文件处理
当测序得到的fastq文件map到基因组之后,我们通常会得到一个sam或者bam为扩展名的文件.SAM的全称是sequence alignment/map format.而BAM就是SAM的二进制文件 ...
随机推荐
- 改善深层神经网络-week3编程题(Tensorflow 实现手势识别 )
TensorFlow Tutorial Initialize variables Start your own session Train algorithms Implement a Neural ...
- 开关电源(DC-DC)与LDO电源的区别---纹波
https://blog.csdn.net/edadoc2013/article/details/78435775
- 必备的60个常用的Linux命令
Linux必学的60个命令Linux提供了大量的命令,利用它可以有效地完成大量的工 作,如磁盘操作.文件存取.目录操作.进程管理.文件权限设定等.所以,在Linux系统上工作离不开使用系统提供的命令. ...
- Linux C语言链表你学会了吗?
链表是一种常见的基础数据结构,结构体指针在这里得到了充分的利用.链表可以动态的进行存储分配,也就是说,链表是一个功能极为强大的数组,他可以在节点中定义多种数据类型,还可以根据需要随意增添,删除,插入节 ...
- Java:检查异常与未检查异常
一.异常的介绍 Throwable 是 Java 中所有错误和异常的超类.Java 虚拟机仅抛出属于此类(或其子类之一)的实例对象,或者是 throw 语句也可以抛出该对象.同样,catch 子句中的 ...
- Tarjan算法 求 有向图的强连通分量
百度百科 https://baike.baidu.com/item/tarjan%E7%AE%97%E6%B3%95/10687825?fr=aladdin 参考博文 http://blog.csdn ...
- Flink 实践教程 - 入门(4):读取 MySQL 数据写入到 ES
作者:腾讯云流计算 Oceanus 团队 流计算 Oceanus 简介 流计算 Oceanus 是大数据产品生态体系的实时化分析利器,是基于 Apache Flink 构建的具备一站开发.无缝连接. ...
- 一维前缀和 连续数组和为k
给定一个整数数组和一个整数 k ,请找到该数组中和为 k 的连续子数组的个数. 滑动窗口没办法解决有负数的情况 方法一: 预处理 前缀和 sum_ij = preSum[j] - preSum[i-1 ...
- Java日期API
JDK8之前日期时间API java.util.Date类 表示特定的瞬间,精确到毫秒 构造器: Date():使用无参构造器创建的对象可以获取本地当前时间. Date(long date) 常用方法 ...
- Django笔记&教程 6-3 使用模型(models)创建表单(form)
Django 自学笔记兼学习教程第6章第3节--使用模型(models)创建表单(form) 点击查看教程总目录 本文参考:Forms for models 1 - 初步介绍 很多时候,我们使用的表单 ...