后缀自动机SAM
某神犇:“初三还不会后缀自动机,那就退役吧!”
听到这句话后,我的内心是崩溃的。
我还年轻,我还不想退役……于是,我在后来,努力地学习后缀自动机。
终于,赶在初三开学前,我终于学会了后缀自动机。
好开心~~~
后缀自动机是一个很奇妙的东西。这是一个处理字符串的神器。如果可以灵活运用,就会有无限的奥妙。
这个东西,说真的,非常不好懂。网上的各种资料,想找到属于自己的,是一件非常难的事情。好不容易地,我找到几个资料,在这里分享一下。
另外,在学后缀自动机的时候,我充分地领悟了一个道理——千言万语不如一标。后缀自动机这种东西,稍微了解概念后,仔细看看标程,那么就会很快的理解做法。至于理解它是为什么……打多了就有感觉了。我的理解就有些感性、模糊,但我一定要好好弄清楚,这样才能将后缀自动机发扬光大。
还有,吐槽一点。周围的大神太多了,有的在几个月前已经学了后缀自动机,我对他们的智商感到羡慕不已,甚至是没学后缀数组就会后缀自动机了。看着他们手推后缀自动机时,我有一种很无奈的感觉,当初同一起点的我们已经相差越来越远,遥不可及……
参考资料
从最长公共子串到后缀自动机(LCS->SAM)
后缀自动机构造过程演示
后缀自动机学习总结
后缀自动机长啥样
假如我们将字符串的所有后缀加入一个Trie中,就会是下面这个玩意儿(以aabbab
为例)
这么大棵树,当字符串很大时,一定建不成这棵树。
我们可以发现,其实这些后缀是有很多相似之处的。
那么怎么办?
当然是,压!
怎么压?
有个叫后缀树的东西,但是,这不是今天的重点。
我们将它压起来,将它压成一个有向无环图。
你可以发现,在后缀自动机上,从起点开始跑,每个路径都能够表示不同的子串。
上面的那棵Trie上的每一个节点所表示的字符串(即原字符串的子串)都可以在后缀自动机上表示出来。
这个后缀自动机是能压到什么程度?
我可以告诉你,它的时空复杂度是O(n)" role="presentation" style="position: relative;">O(n)O(n)的。
一些概念
节点所代表的含义
后缀自动机中,每一个节点表示子串的right" role="presentation" style="position: relative;">rightright集合相同的子串集合。
啊哈?啥意思?
一个子串的right" role="presentation" style="position: relative;">rightright集合,表示的是这个子串在字符串中结束位置集合。
而SAM上的每个节点,表示的是right" role="presentation" style="position: relative;">rightright集合相同的子串集合。
思考一下这个集合的性质。
很显然,因为所有结束位置相同,对于这个集合中任意两个子串,一个子串必定是另一个的后缀。
比如说,abcabcabc
。
那么子串,abc
、bc
、c
在同一个集合之中。
同时我们发现,这个集合中的所有的子串,都是集合中最大的子串的后缀,并且这些后缀是连续的。也就是说,如果abc
和c
在同一集合,bc
不可能不在这个集合。
那么每个节点所代表的子串有个长度的范围,记为minleni" role="presentation" style="position: relative;">minleniminleni和maxleni" role="presentation" style="position: relative;">maxlenimaxleni。
对于字符串abcabcabcc
(注意和上面的例子不同),abc
和bc
在同一集合中,则范围是[2,3]" role="presentation" style="position: relative;">[2,3][2,3]。
还有,两个不同的节点,它们表示的子串集合不相交,而且right" role="presentation" style="position: relative;">rightright集要么一个真包含另一个,要么不相交。
fail指针
既然是自动机,那怎么可能没有fail" role="presentation" style="position: relative;">failfail指针呢?
对于一个节点,它的fail" role="presentation" style="position: relative;">failfail指针只有一个(废话!)。
fail" role="presentation" style="position: relative;">failfail指针指向的节点的right" role="presentation" style="position: relative;">rightright集合真包含当前节点的right" role="presentation" style="position: relative;">rightright集合。而且fail" role="presentation" style="position: relative;">failfail指针指向的节点的集合的maxlen" role="presentation" style="position: relative;">maxlenmaxlen最大。
再思考一下有哪些性质。
right" role="presentation" style="position: relative;">rightright集合真包含当前节点的right" role="presentation" style="position: relative;">rightright集合,那么fail" role="presentation" style="position: relative;">failfail指向节点表示的字符串集必定都是当前节点的字符串集中任意一个字符串的后缀。
又因为maxlen" role="presentation" style="position: relative;">maxlenmaxlen最大,所以当前节点的最短后缀和它的最长后缀一定是连续的。也就是说,failmaxlen+1=thisminlen" role="presentation" style="position: relative;">failmaxlen+1=thisminlenfailmaxlen+1=thisminlen。所以minlen" role="presentation" style="position: relative;">minlenminlen是不需要记录的。
对于字符串abcabcabcc
,表示abc
和bc
的节点的fail" role="presentation" style="position: relative;">failfail指针指向的是表示c
的节点。
因为right" role="presentation" style="position: relative;">rightright集合真包含,所以,fail" role="presentation" style="position: relative;">failfail指针形成了一棵树,称为fail" role="presentation" style="position: relative;">failfail树。
上图是aabbab
的后缀自动机,黑色的是转移边,蓝色的是fail" role="presentation" style="position: relative;">failfail边。
这幅图来自从最长公共子串到后缀自动机(LCS->SAM),在此再次感谢这个博主。不过为什么有水印……
后缀自动机的构建
增量法,即每一次插入一个字符。
先放标程,千言万语不如一标。
SAM的标程贼短,而且实用。比SA还好打。
#define MAXN 100000
struct Node{
int len;
Node *c[26];
Node *fail;
} d[MAXN*2+10];
Node *null,*last,*S;
int cnt;
void sam_init(){
null=&d[0];
++cnt;
d[cnt].len=0;
d[cnt].fail=null;
S=last=&d[cnt];
}
void sam_insert(int ch){
Node *now=&d[++cnt];
now->len=last->len+1;
Node *p;
for (p=last;p && !p->c[ch];p=p->fail)
p->c[ch]=now;
if (!p)
now->fail=S;
else{
Node *q=p->c[ch];
if (p->len+1==q->len)
now->fail=q;
else{
Node *clone=&d[++cnt];
clone->len=p->len+1;
memcpy(clone->c,q->c,sizeof q->c);
clone->fail=q->fail;
for (;p && p->c[ch]==q;p=p->fail)
p->c[ch]=clone;
now->fail=q->fail=clone;
}
}
last=now;
}
再描述一下这个过程。
1. 新建一个节点,设这个节点为u" role="presentation" style="position: relative;">uu。
2. 从last" role="presentation" style="position: relative;">lastlast(最后一个点)开始,沿着fail" role="presentation" style="position: relative;">failfail边跳。如果没有ch" role="presentation" style="position: relative;">chch的转移边,则向u" role="presentation" style="position: relative;">uu连上一条边。否则退出。
3. 如果一直未找到ch" role="presentation" style="position: relative;">chch的转移边,那么fail" role="presentation" style="position: relative;">failfail指向初始点S" role="presentation" style="position: relative;">SS。否则进入4。
4. 设这个点为p" role="presentation" style="position: relative;">pp,转移边连向的点为q" role="presentation" style="position: relative;">qq。如果pmaxlen+1=qmaxlen" role="presentation" style="position: relative;">pmaxlen+1=qmaxlenpmaxlen+1=qmaxlen,就直接将u" role="presentation" style="position: relative;">uu的fail" role="presentation" style="position: relative;">failfail指向q" role="presentation" style="position: relative;">qq。否则进入5。
5. 新建一个节点clone" role="presentation" style="position: relative;">cloneclone,clonemaxlen=pmaxlen+1" role="presentation" style="position: relative;">clonemaxlen=pmaxlen+1clonemaxlen=pmaxlen+1,clone" role="presentation" style="position: relative;">cloneclone连向q" role="presentation" style="position: relative;">qq连向的所有节点,并且clone" role="presentation" style="position: relative;">cloneclone的fail" role="presentation" style="position: relative;">failfail指针指向p" role="presentation" style="position: relative;">pp,进入6。
6. p" role="presentation" style="position: relative;">pp继续沿着fail" role="presentation" style="position: relative;">failfail跳,所有指向q" role="presentation" style="position: relative;">qq的指针全都指向clone" role="presentation" style="position: relative;">cloneclone。最后将u" role="presentation" style="position: relative;">uu和q" role="presentation" style="position: relative;">qq的fail" role="presentation" style="position: relative;">failfail指向clone" role="presentation" style="position: relative;">cloneclone。
7. 最后,更新last" role="presentation" style="position: relative;">lastlast。
原理?
首先,last" role="presentation" style="position: relative;">lastlast是先前最后的节点,而且maxlen" role="presentation" style="position: relative;">maxlenmaxlen是最长的。在fail" role="presentation" style="position: relative;">failfail树上,last" role="presentation" style="position: relative;">lastlast到S" role="presentation" style="position: relative;">SS的链上所有节点的集合,是前面的字符串的所有后缀。
所以,非常自然的,用链上的节点来连向新的节点。
上面的步骤中,主要讲一下步骤4~6。其它的比较易懂。
如果pmaxlen+1=qmaxlen" role="presentation" style="position: relative;">pmaxlen+1=qmaxlenpmaxlen+1=qmaxlen,那么q" role="presentation" style="position: relative;">qq所代表的子串只有一个,所以u" role="presentation" style="position: relative;">uu的fail" role="presentation" style="position: relative;">failfail指向q" role="presentation" style="position: relative;">qq理所当然。
否则,新开一个节点,强行clonemaxlen=pmaxlen+1" role="presentation" style="position: relative;">clonemaxlen=pmaxlen+1clonemaxlen=pmaxlen+1,然后替代q" role="presentation" style="position: relative;">qq的位置(当然,不意味着q" role="presentation" style="position: relative;">qq没了)。让u" role="presentation" style="position: relative;">uu和q" role="presentation" style="position: relative;">qq都指向它。
我怎么好像是在念过程啊……好吧,这一段我理解得比较感性,朦胧,不好描述。最好看上面的参考资料。
复杂度?
每次加入一个字符最多增加两个节点,所以节点个数是线性的。
边是线性的,整个建SAM的过程都是线性的……(我当然不会证)。
反正时空复杂度O(n)" role="presentation" style="position: relative;">O(n)O(n)。
记住就好。
还有代码复杂度,简单易打,是吧……
例题
现在我打出了三道例题。
一个是洛谷上的模板。
一个是KMP裸题。
还有一个是求不同子串的第k" role="presentation" style="position: relative;">kk大。
这些都是简单的模板题。
这篇博客有很多好例题,只不过我初学,要有个慢热的过程。以后有时间就做
后缀自动机SAM的更多相关文章
- [转]后缀自动机(SAM)
原文地址:http://blog.sina.com.cn/s/blog_8fcd775901019mi4.html 感觉自己看这个终于觉得能看懂了!也能感受到后缀自动机究竟是一种怎样进行的数据结构了. ...
- 【算法】后缀自动机(SAM) 初探
[自动机] 有限状态自动机的功能是识别字符串,自动机A能识别字符串S,就记为$A(S)$=true,否则$A(S)$=false. 自动机由$alpha$(字符集),$state$(状态集合),$in ...
- SPOJ 1811. Longest Common Substring (LCS,两个字符串的最长公共子串, 后缀自动机SAM)
1811. Longest Common Substring Problem code: LCS A string is finite sequence of characters over a no ...
- 后缀自动机SAM学习笔记
前言(2019.1.6) 已经是二周目了呢... 之前还是有一些东西没有理解到位 重新写一下吧 后缀自动机的一些基本概念 参考资料和例子 from hihocoder DZYO神仙翻译的神仙论文 简而 ...
- 浅谈后缀自动机SAM
一下是蒟蒻的个人想法,并不很严谨,仅供参考,如有缺误,敬请提出 参考资料: 陈立杰原版课件 litble 某大神 某大神 其实课件讲得最详实了 有限状态自动机 我们要学后缀自动机,我们先来了解一下自动 ...
- 后缀自动机(SAM)奶妈式教程
后缀自动机(SAM) 为了方便,我们做出如下约定: "后缀自动机" (Suffix Automaton) 在后文中简称为 SAM . 记 \(|S|\) 为字符串 \(S\) 的长 ...
- 【算法】后缀自动机(SAM) 例题
算法介绍见:http://www.cnblogs.com/Sakits/p/8232402.html 广义SAM资料:https://www.cnblogs.com/phile/p/4511571.h ...
- 后缀自动机(SAM)速成手册!
正好写这个博客和我的某个别的需求重合了...我就来讲一讲SAM啦qwq 后缀自动机,也就是SAM,是一种极其有用的处理字符串的数据结构,可以用于处理几乎任何有关于子串的问题,但以学起来异常困难著称(在 ...
- 【算法专题】后缀自动机SAM
后缀自动机是用于识别子串的自动机. 学习推荐:陈立杰讲稿,本文记录重点部分和感性理解(论文语言比较严格). 刷题推荐:[后缀自动机初探],题目都来自BZOJ. [Right集合] 后缀自动机真正优于后 ...
- 【文文殿下】对后缀自动机(SAM)的理解
后缀自动机,是一种数据结构,是由状态和转移关系构成的.它虽然叫做后缀自动机,可是他却与后缀并没有什么太大的联系. 后缀自动机的每一种状态都是原串的一些子串的集合,每个子串只唯一存在于某个状态中,对每一 ...
随机推荐
- [JZOJ3320] 【BOI2013】文本编辑器
题目 题目大意 给你一个文本,要删去其中所有的'e'. 有三种操作: h光标左移. x删除光标上面的字母(光标是横着的). fc跳到后面的第一个字符为'c'的位置. 问操作序列的最短长度. 思考历程 ...
- 计算几何——判线段规范相交+最短路zoj1721
枚举每个端点,然后i点j点连线作为一条路径,逐一判断这条路径是否可行即可 注意的地方:判一条线段是否可行,需要判其余线段是否和其相交,但是这个相交比较难判(因为会不规范相交),所以将问题转化为墙以外的 ...
- 使用pdf文本域模板生成对应的pdf
第一步: 下载jar包 <!-- itext的pdf的依赖--> <dependency> <groupId>com.itextpdf</groupId> ...
- (转)Android 自定义标题栏(title栏)
转:http://blog.csdn.net/jamin0107/article/details/6715678 第一步,向实现自定义标题栏,需要在onCreate方法里这样写 requestWind ...
- python基于SMTP发送邮件
import smtplib from email.header import Header from email.mime.text import MIMEText ''' SMTP是发送邮件的协议 ...
- 更改网卡名称以及重启网卡提示Determining if ip address x.x.x.x is already in use for device eth0
安装系统完成后,在CentOS6.6下网卡名称变为em1,有些不太方便,还是改回eth0 修改grub配置文件,vi /boot/grub/grub.conf,增加如下红色字体 kernel /vml ...
- POJ-2752-Seek the Name-kmp的变形
The little cat is so famous, that many couples tramp over hill and dale to Byteland, and asked the l ...
- java中 ++a 与 a++ 的区别
public static void main(String[] args) { int a = 5; a ++; System.out.println(a); int b = 5; ++ b; Sy ...
- Python接口测试框架实战与自动化进阶✍✍✍
Python接口测试框架实战与自动化进阶 整个课程都看完了,这个课程的分享可以往下看,下面有链接,之前做java开发也做了一些年头,也分享下自己看这个视频的感受,单论单个知识点课程本身没问题,大家看 ...
- Win10命令提示符git log中文乱码的解决方案
在系统环境变量中新建一个名为LESSCHARSET的变量 其值为utf-8 新建完毕后应用,git log就不会出现乱码的问题了^_^ 参考博文:git- win10 cmd git log 中文乱码 ...