正好写这个博客和我的某个别的需求重合了。。。我就来讲一讲SAM啦qwq

后缀自动机,也就是SAM,是一种极其有用的处理字符串的数据结构,可以用于处理几乎任何有关于子串的问题,但以学起来异常困难著称(在机房里,最先学会SAM的永远是大佬(比如litble和zyf(他在退役前就学了)))。

但是!!!当你学了SAM并熟练地刷了几道题后,你会发现——你之前为了学SAM而强行理解的许多定理,对你应用SAM一点用处也没有!为了引出构造算法,几乎所有博客都会详细地解释“你为啥要这样做”,然鹅。。。

SAM完全可以当成黑盒来用!!!!!!

所以我打算写一篇SAM速成博客。。。保证即学即会!

在构建之前你不得不知道的

Warning:想彻底理解后缀自动机吗?那你有好消息了!请立即关闭此页面,在百度里搜索“后缀自动机 陈立杰”,开始愉快的学习吧!(讲真,陈老师的ppt是讲的最好的,别的博客无能出其右者)

SAM是一个DAG(有向无环图),每个点代表一个"状态",边代表状态转移,边上有一个字母。SAM有一个起始状态(称为起点),从起点开始,沿着边不断走下去,就可以得到一个字符串。记当前停留节点为\(x\),走出来的字符串为\(S\),称节点\(x\)可代表字符串\(S\)。记\(x\)可代表的串中长度最长的串的长度为\(len(x)\)。

另外,除起点外的每个节点还拥有一个“后缀链接“,记作\(fa(x)\)。后缀链接组成了一棵树,别的性质在构建完之后再讲。

存储SAM利用的是类似于Trie树的存储结构,即使用\(ch[][26]\)数组保存状态转移的边。

知道了这些,构建SAM的工作就可以开始了。

开始建造后缀自动机

准备工作:建立数组\(ch,fa,len\),准备指针\(last,cnt\)。SAM的构造方法是不断地向已经建好的SAM中加入新的节点。\(last\)表示上一个被插入的节点,\(cnt\)表示SAM中的节点数量。一开始,\(last=cnt=1\),表示只有一个起点的初始SAM。

接下来,假设要往SAM里加入一个字符\(x\)。

  1. 新建节点\(np=++cnt\)。新建节点\(p\)。\(p=last\)。$ last=np$。
  2. 如果不存在\(ch[p][x]\),令\(ch[p][x]=np,p=fa[p]\)。重复此步骤。
  3. 如果到最后还没有一个\(p\)拥有儿子\(x\),令\(fa[np]=1\)。退出过程。
  4. 当\(ch[p][x]​\)出现时,令\(q=ch[p][x]​\)。如果\(len[q]==len[p]+1​\),令\(fa[np]=q​\)。退出过程。
  5. 否则有点麻烦。新建节点\(nq=++cnt\),将\(q\)的儿子都复制给\(nq\),令\(len[nq]=len[p]+1\)。
  6. 令\(fa[nq]=fa[q],fa[q]=fa[np]=nq\)。
  7. 从\(p\)开始沿着后缀链接,将所有\(ch[p][x]==q\)的节点的\(ch[p][x]\)都替换成\(nq\)。

将你的字符串的所有字符都一一进行如上操作后,你就得到了用你的字符串构建出来的SAM。

你不需要知道为什么这么操作可以得到SAM,你只需要记下以下的代码,做几道题强化记忆,然后就可以用SAM的性质来秒题了。

void insert(int x)
{
int np=++cnt,p=last;
len[np]=len[p]+1,last=np;
while(p&&!ch[p][x])ch[p][x]=np,p=fa[p];
if(!p)fa[np]=1;
else
{
int q=ch[p][x];
if(len[q]==len[p]+1)fa[np]=q;
else
{
int nq=++cnt;len[nq]=len[p]+1;
memmove(ch[nq],ch[q],sizeof(ch[nq]));
fa[nq]=fa[q],fa[np]=fa[q]=nq;
while(ch[p][x]==q)ch[p][x]=nq,p=fa[p];
}
}
}

后缀自动机的奇妙性质

现在,你已经拥有SAM了,你需要知道它有什么用。这里列举了SAM的一些基本且常用的性质。

请牢记以下每一条内容!都十分有用!不要去问“为什么是这样的”!(如果一定要问,请参照上文蓝色放大的Warning)

首先,SAM的点数与边数都是\(O(n)\)的。记住,由于每次插入最多新建两个点,所以应该开字符总量两倍的空间。计算空间时别忘了你开了26倍的\(ch\)数组。

在SAM上从起点开始沿着边随便走走,得到的一定是子串。同时,每一个子串都可以在SAM上走出一条唯一对应的路径。也就是说,子串和SAM上从起点开始的每一条路径一一对应。路径数等于子串数。

起点可以看做是代表空串的点。

重点:定义子串的\(right\)集合:这个子串在原串中所有出现的位置的右端点的集合。

比如说:AAAABBAAAAABAAABBAA

子串AAB出现了3次,右端点集合为\(\{5,12,16\}\)。这就是子串AAB的\(right\)集合。

一个节点能够代表的所有子串的\(right\)集合是一样的。\(right\)集合相等的子串一定被同一个节点代表。(所以,我们会使用“节点的\(right\)集合”这个说法。)两个节点的\(right\)集合之间要么真包含,要么没有交集。若节点\(y\)的\(right\)集合包含了节点\(x\)的\(right\)集合,那么\(y\)能代表的子串均为\(x\)能代表的子串的真后缀。

重点:定义节点\(x\)的后缀链接\(fa(x)\):如果有一些节点的\(right\)集合包含了\(x\)的\(right\)集合,\(fa(x)\)是其中\(right\)集合的大小最小的那一个。

后缀链接们组成了一棵“后缀链接树”(不是后缀树)。后缀链接树的根为起点。若节点\(y\)的\(right\)集合包含了节点\(x\)的\(right\)集合,那么\(y\)在后缀链接树上是\(x\)的祖先。

一个节点的\(right\)集合等于他在后缀链接树上的所有儿子的\(right\)集合的并集。而且儿子的\(right\)集合之间两两没有交集。

每个节点能代表的子串的长度范围是一段连续的区间。这很好理解,因为它们的结束位置都是相同的。

我们求出每个节点能代表的最长串的长度(即\(len(x)\))了,那最短长度呢?其实就等于后缀父亲节点的\(len+1\)。也就是说,所有本质不同的子串的数量等于\(\sum len(x)-len(fa(x))\)。

总结

以上就是SAM的基本性质~对于一道特定的题,你可能需要通过上面的性质推出你需要的新性质。如果你还有什么疑问可以向我留言,我(在退役前)会在一天之内回复的!(你也可以去问更强的boshi和litble,别去问zyf因为他已经退役了。)

题单我就不给了,因为网上有很多很多。。。

当然,如果你立志要当大佬。。。那赶紧打开陈立杰的ppt吧=。=

感谢您的观看qwq!

最新文章

  1. 01_iOS开发需要准备什么?
  2. GFF format
  3. Android开发之ListView-SimpleAdapter的使用
  4. TCP/IP三次握手
  5. 今日网站突然报错,mysql的故障
  6. Js浏览器对象
  7. (转)java多线程的一篇好文
  8. Lucene.Net 2.3.1开发介绍 —— 简介
  9. Qt5 OpenGL框架
  10. java代码之美(2)---Java8 Stream
  11. Git通过密钥对远程仓库上传和更新详细操作
  12. sqlserver 将 “用 特定字符 分隔的一个字段” 拆分成多个字段,然后两个表之间数据更新
  13. photoshop 笔记
  14. mysql_connect
  15. [分享] 采用opencv_cascadetrain进行训练的步骤及注意事项 [复制链接]
  16. IDEA永久激活
  17. 框架中spring有专门的类用于处理乱码
  18. 我看到的最棒的Twisted入门教程!
  19. 【BZOJ2554】Color 概率神题
  20. 【python 】Requests 库学习笔记

热门文章

  1. 【刷题】LOJ 6223 「网络流 24 题」汽车加油行驶问题
  2. 【刷题】LOJ 6006 「网络流 24 题」试题库
  3. PyCharm远程开发配置及一些问题的解决方案
  4. go gcc
  5. opencv ---getRotationMatrix2D函数
  6. 个推应用统计产品(个数)Android集成实践
  7. Java基础-SSM之mybatis一对多和多对一关系映射
  8. webstorm去掉vue错误提示
  9. nodejs安装zmq出错
  10. SQL Server 基础之《学生表-教师表-课程表-选课表》(二)