S A M SAM SAM学的太烂了,滚粗了。

让我们从头说起。字符串 s s s的 S A M SAM SAM是一个接受 s s s的所有后缀的最小 D F A DFA DFA。既然是 D F A DFA DFA那么就存在一个或多个终止状态,如果我们从初始状态 t 0 t_0 t0​出发,最终转移到了一个终止状态,则路径上的所有转移连接起来一定是字符串 s s s的一个后缀。反过来, s s s的每个后缀均可用一条从 t 0 t_0 t0​到某个终止状态的路径构成。

S A M SAM SAM包含关于字符串 s s s的所有子串信息。任意从初始状态 t 0 t_0 t0​开始的路径,如果我们将转移路径上的标号写下来,都会形成 s s s的一个子串。反之每个 s s s的子串对应从 t 0 t_0 t0​开始的某条路径。唯一需要注意的是若干个子串可能对应同一条路径,因为是去重过后的。

考虑字符串 s s s的任意非空子串 t t t,记 endpos(t) \text{endpos(t)} endpos(t)为在字符串 s s s中 t t t的所有结束位置。这样所有字符串 s s s的非空子串都可以根据它们的 endpos \text{endpos} endpos集合被分为若干个 等价类

引理1:字符串 s s s的两个非空子串 u u u和 w w w(假设 ∣ u ∣ ≤ ∣ w ∣ |u|\le |w| ∣u∣≤∣w∣)的 endpos \text{endpos} endpos相同,当且今当字符串 u u u在 s s s中的每次出现,都是以 w w w的后缀形式存在的。

引理2:考虑两个非空子串 u u u和 w w w(假设 ∣ u ∣ ≤ ∣ w ∣ |u|\le |w| ∣u∣≤∣w∣),那么如果 u u u是 w w w的一个后缀,则 endpos ( w ) ⊆ endpos ( u ) \text{endpos}(w)\subseteq \text{endpos}(u) endpos(w)⊆endpos(u);否则 endpos ( w ) ∩ endpos ( u ) = ∅ \text{endpos}(w)\cap \text{endpos}(u)=\empty endpos(w)∩endpos(u)=∅。

引理3:考虑一个 endpos \text{endpos} endpos等价类,将类中的所有子串按长度非递增的顺序排序。那么对于同一等价类的任意两子串,较短者为较长者的后缀,且该等价类中的子串长度 恰好覆盖整个区间 [ x , y ] [x,y] [x,y]

这些东西就不用证了吧。。。

考虑 S A M SAM SAM中某个不是 t 0 t_0 t0​的状态 v v v。我们已经知道,状态 v v v对应具有相同 endpos \text{endpos} endpos的等价类。我们如果定义 w w w为这些字符串中最长的一个,则所有其他的字符串都是 w w w的后缀。定义一个后缀连接 link(v) \text{link(v)} link(v)连接道对应于 w w w的最长后缀的另一个 endpos \text{endpos} endpos的等价类状态。

为了方便,规定 endpos ( t 0 ) = { − 1 , 0 , . . , ∣ S ∣ − 1 } \text{endpos}(t_0)=\{-1,0,..,|S|-1\} endpos(t0​)={−1,0,..,∣S∣−1}。

引理4:所有后缀链接构成一颗根节点为 t 0 t_0 t0​的树。唯一需要注意的是这颗树上的节点也对应这个 D A G DAG DAG上的点。证明也非常简单,考虑每次会连接到长度更短的后缀,最后总能到达空串对应的初始状态 t 0 t_0 t0​。

引理5:通过 endpos \text{endpos} endpos集合构造的树(每个子结点的 s u b s e t subset subset都包含在父节点的 s u b s e t subset subset中)与通过后缀连接 link \text{link} link构造的树相同。换句话说,后缀连接构成的树本质上是 endpos \text{endpos} endpos集合构成的一棵树。事实上仔细观察一下还会发现一个节点的 endpos \text{endpos} endpos就是所有子结点的 endpos \text{endpos} endpos集合的并。关于任意节点的 endpos \text{endpos} endpos集合怎么求我们后面会提到。

然后介绍一下 S A M SAM SAM的构造过程。这比较繁琐,但也是必须的。

考虑给当前字符串添加一个字符 c c c的过程。

  • 令 l a s t last last为添加字符 c c c之前,整个字符串对应的状态。
  • 创建一个新的状态 c u r cur cur,并将 len ( c u r ) \text{len}(cur) len(cur)赋值为 len ( l a s t ) + 1 \text{len}(last)+1 len(last)+1,此时 link ( c u r ) \text{link}(cur) link(cur)还未知。
  • 从状态 l a s t last last开始,如果还没有到字符 c c c的转移,那么就添加一个道状态 c u r cur cur的转移,遍历后缀链接。如果在某个点已经存在到字符 c c c的转移,我们就停下来,并将这个状态标记为 p p p。
  • 如果没有找到这样的状态 p p p,我们就到达了虚拟状态 − 1 -1 −1,将 link ( c u r ) \text{link}(cur) link(cur)赋值为 0 0 0并退出。
  • 假设现在我们找到了一个状态 p p p,其可以通过字符 c c c转移。将转移到的状态标记为 q q q。事实上此时我们已经知道 c u r cur cur的后缀链接的长度应该为 len ( p ) + 1 \text{len}(p)+1 len(p)+1了。所以我们只需要进行一些修正即可。
  • 如果 len ( p ) + 1 = len ( q ) \text{len}(p)+1=\text{len}(q) len(p)+1=len(q),那么只要将 link ( c u r ) \text{link}(cur) link(cur)赋值为 q q q并退出。
  • 否则创建一个新的状态 c l o n e clone clone,复制 q q q的除 l e n len len外的所有信息(后缀链接和转移)。将 len ( c l o n e ) \text{len}(clone) len(clone)赋值为 len ( p ) + 1 \text{len}(p)+1 len(p)+1。注意这个时候应该是 c l o n e clone clone为 q q q和 c u r cur cur的后缀,所以将后缀链接从 c u r cur cur指向 c l o n e clone clone,也从 q q q指向 c l o n e clone clone。最后还要完成一个重定向的过程。使用后缀链接从状态 p p p往回走,只要存在一条通过 p p p到达 q q q的转移,就将该转移重定向到状态 c l o n e clone clone。这也很好理解,因为 c l o n e clone clone和 q q q的区别就在于 c l o n e clone clone的 endpos \text{endpos} endpos集合里面多了一个位置,因为是从 p p p往回走所以转移到的状态长度肯定不会超过 c l o n e clone clone,所以应该和 c l o n e clone clone放在同一个等价类当中。
  • 最后将 l a s t last last的值更新为状态 c u r cur cur。

有一点复杂,但是如果多画图还是可以理解的。

事实上 S A M SAM SAM的节点个数不超过 2 n − 1 2n-1 2n−1,转移数不超过 3 n − 4 3n-4 3n−4。有构造能卡到上界,这里就不赘述了。我更喜欢将转移数看 D A G DAG DAG中的边数,这似乎在提示我们可以直接在 D A G DAG DAG上做文章。

简单讲几个比较基础的应用吧。

检查字符串是否出现:直接根据模式串 P P P的字符进行转移即可。但是我要提的是,这个算法还可以找到 P P P在文本串中出现的最大前缀长度。

不同子串个数:转化成不同路径条数,直接在 D A G DAG DAG上统计即可。当然直接对每个节点对应的子串数目求和也是可以的。

字典序第 K K K大串:利用路径和子串的对应关系不难贪心求出答案。但是我要提的是,如果相同的子串算出现多次,那么我们可以通过递推求出每个节点对应的 endpos \text{endpos} endpos集合的大小,相当于给每个子串赋了一个权值,可以类似计算。

最小循环移位:发现 S + S S+S S+S包含字符串 S S S的所有循环移位作为子串,那么问题等价于找一条长度为 n n n的字典序最小的路径,这显然可以贪心解决。

所有出现位置:说白了就是要找到所有节点对应的 endpos \text{endpos} endpos。可以这样来想,如果一个子串的 endpos \text{endpos} endpos集合中包含 i i i,那么说明这个子串是以 i i i结尾的前缀的后缀,换句话说在后缀树上是 i i i对应的终止节点的祖先,那么就在 i i i对应的终止节点上插入 i i i,然后线段树合并即可。显然这里也可以用上可持久化数据结构做到在线。

最短的没有出现的字符串:这个可以通过 D P DP DP解决。就不再赘述了。

两个子串的最长公共前缀:这个也非常简单。将 S S S反转一下,问题变成了求公共后缀,而根据分析我们知道这个长度就是后缀树上 L C A LCA LCA对应的 len \text{len} len。

不得不感叹, S A M SAM SAM真是字符串工具的集大成者。之前没认真学真是太可惜了。

让我们尝试用 S A M SAM SAM来解决两道题目吧,不然就全变成抄OI-wiki了。

例题后面会更。

【学习笔记】SAM的结构和应用相关推荐

  1. 5G NR学习笔记:帧结构和物理资源

    5G NR学习笔记:帧结构和物理资源 参数集(numerology) 与LTE的参数集(子载波间隔和符号长度)不同,NR支持不同的子载波间隔(Sub-Carrier Spacing, SCS),所有的 ...

  2. Solr6.7 学习笔记(01) -- 目录结构

    Solr解压后的目录结构 --contrib: Solr的一些扩展 --analysis-extras: 包含一些文本分析组件及其依赖 --clustering: 包含一个用于集群搜索结果的引擎 -- ...

  3. Lawliet|C语言学习笔记5——循环结构

    C语言学习笔记--循环结构 1.求1+2+3+-+100 #include<stdio.h> int main() {int i=1,sum=0; //定义变量i的初值为1,sum的初值为 ...

  4. Lawliet|C语言学习笔记3——顺序结构

    C语言学习笔记--顺序结构 计算a+b #include<stdio.h> int main() {int a,b,sum;scanf("%d%d",&a,&a ...

  5. Lawliet|C语言学习笔记4——选择结构

    C语言学习笔记--选择结构 求一元二次方程的根 简约版 #include<stdio.h> #include<math.h> //程序中要调用求平方根函数sqrt int ma ...

  6. 《数据结构》c语言版学习笔记——单链表结构(线性表的链式存储结构Part1)

    线性表的链式存储结构 数据结构系列文章 第二章 单链表结构 文章目录 线性表的链式存储结构 前言 一.单链表的建立 代码 二.单链表的读取 代码 三.单链表的插入 代码 四.单链表的删除 代码 五.单 ...

  7. python编程语法-Python学习笔记(Ⅰ)——Python程序结构与基础语法

    作为微软的粉丝,最后终于向Python低头了,拖了两三个月终于下定决心学习Python了.不过由于之前受到C/C#等语言影响的思维定式,前期有些东西理解起来还是很费了些功夫的. 零.先抄书: 1.Py ...

  8. Python学习笔记(Ⅰ)——Python程序结构与基础语法

    作为微软的粉丝,最后终于向Python低头了,拖了两三个月终于下定决心学习Python了.不过由于之前受到C/C#等语言影响的思维定式,前期有些东西理解起来还是很费了些功夫的. 零.先抄书: 1.Py ...

  9. 知识图谱学习笔记-非结构化数据处理

    非结构话数据到知识图谱 非结构数据-> 信息抽取(命名实体识别.关系抽取)-> 图谱构建(实体消歧.链接预测)-> 图分析算法 一.文本分析关键技术 拼写纠错 分词 词干提取 词的过 ...

  10. C++ STL学习笔记(2) 容器结构与分类

    接着学习侯捷老师的C++ STL! 在使用容器的时候,需要明白容器中元素之间在内存里的关系是什么样的,是连续的,还是非连续的. 容器可以分为两类: 1. sequence container , 即序 ...

最新文章

  1. 6位有符号补码阵列乘法器_C/C++学习日记:原码、反码和补码
  2. linux mysql 1045 错误_Linux 下,mysql数据库报无法登陆错误:ERROR 1045 (28000): Access denied for use...
  3. 他花了一个月,使用MicroPython将自己装进OLED里面
  4. Http接口开发(自测服务端客户端)
  5. ruby应用:puppet
  6. symmetric-tree
  7. Java基础day23
  8. openwrt 19 overlay 空间不足_重视 | 山西一矿井瓦斯爆炸,有限空间作业切记注意安全...
  9. 计算机组成原理试题 t4,计算机组成原理(四版)本科生试题库整理附答案
  10. sop4封装尺寸图_「光电封装」 有源光器件的结构和封装
  11. linux服务器重启ctrl,Linux禁止Ctrl+Alt+Del重启
  12. java 求向量的均值,标准数组——向量
  13. 小任务之使用SVG画柱状图~
  14. java程序开发的流程_Java程序开发流程(图文解说版)
  15. 笔试12:Bootstrap知识
  16. 十二生肖的相合、相冲、相刑、相害
  17. node-webkit:开发桌面+WEB混合型应用的神器
  18. 轻雀协作客户最佳实践之凯叔讲故事
  19. 各种Hash函数和代码
  20. Exception caught: mqAdminExt get broker stats data TOPIC_PUT_NUMS failed

热门文章

  1. python实现图片格式转换_python实现批量图片格式转换
  2. python规定的函数头部_Python基础手册23——函数的调用
  3. ChatGPT中文版源码
  4. PyInstaller指定打包的Python版本
  5. [学习笔记]黑马程序员Spark全套视频教程,4天spark3.2快速入门到精通,基于Python语言的spark教程
  6. 【最新消息】苹果放出新大招??!!
  7. QTP11.00支持的IE版本
  8. linux 卸载java jdk1.6_linux下查看已经安装的jdk 并卸载jdk
  9. 二级c语言考试真题及答案,全国计算机二级C语言考试真题及答案.doc
  10. 并联四足机器人项目开源教程(五) --- 四足机器人相关书籍论文研读