后缀自动机 小结
来源:互联网 发布:网络进度计划波浪线 编辑:程序博客网 时间:2024/06/08 06:22
以下主要来自CLJ的ppt,整理并添加了一些附注(
错误讲解)
以下皆为口胡,为了装逼掉RP而搞成高端(?)的形式。
神犇轻喷。
Markdown面对长博文,学校的电脑有点力不从心啊。。
1 前序
定义1:
定义2:rev(s):逆序的s。
定义3:
定义4:
定义5:
定义6:
推论1:若
2 Right集合
2.1 集合的唯一性
公理?:对于
推论2:任意两个状态的Right集合不同。同一个状态可以表示多个子串,且这些子串的Right集合相同。
推论3:状态的个数=本质不同的Right集合的个数(初始状态这里不计入)。
推论4:某个子串对应的状态的Right集合大小就是子串的出现次数。如果未对应任何状态则不存在该子串。
2.2 子串的后缀性质
引理1:两个非空子串a和b且
证明:由于a和b可以分别表示为
推论5:如果已知状态s能表示的最长子串,s能表示的所有子串必然是该最长子串的后缀。
2.3 子串的长度性质
定义7:状态
从引理1的证明中可看出些端倪。注意
如果长度越来越小,肯定存在一个下限,使得再减小,Right集的元素就会变多。
如果长度越来越长,肯定存在一个上限,使得再增加,Right集的元素就会变少(想想看?)。
定义8:fa(s),满足
推论6:
证明:当表示的子串长度
这相当于,fa(s)表示的子串,是s表示子串的后缀,而且紧接s所表示的子串,存在是s状态以外能表示的最长的后缀。
推论7:
2.4 状态
引理2:同状态表示的子串的最后一个字符相同。
证明:同状态表示的子串为后缀包含关系。
推论8:若
证明:即从其他状态转移一个字符而来,也就是说,指向状态s的边的字符都一样。由引理2可知,到达状态s的所有路径表示出的子串的最后一个字符相同。
引理3:若
证明:由于Right集合沿fa走总是在扩充的。
引理4: 若
证明:对于
推论9:
证明:由于
2.5 集合的从属性
引理5:两个子串
证明,法1:若
证明,法2:首先要明确的是任两个状态不可以同时表示一个子串(显然,否则匹配子串的时候该走哪一个状态呢?),也就是状态
3 Parent树(F♂A)
定义9:Parent树,由s->fa(s)组成的一棵树。
发现max(s)随着沿fa移动,越来越小,直到0,而初始状态的范围显然是[0,0]。也就是说,沿着fa移动,总会到达初始状态,即由fa构成边组成的树是以初始状态为根的有向树。
Parent树满足,
由关系树可以保存各点的Right集合而且大小线性,dfs序可便捷地查询。
引理6:
证明:可以配合引理2看。由引理4可得,必存在
引理7:Parent树的节点数不超过
证明:从推论2,定义6出发。Parent树的叶子节点的Right集合元素为1个,而非叶子节点至少有2个孩子,得证。
4 性质
4.1 线性
引理8:后缀自动机的转移数不超过
证明:如果对后缀自动机做生成树(和Parent树无关),树边为2n-2条(树节点为2n-1个),考虑非树边,对于转移
推论10:后缀自动机的状态数为
证明:由引理7和引理8得知。
4.2 与后缀树的联系
SAM(s)的Parent树即rev(s)的后缀树。
s的后缀树的转移指针即SAM(s)中的转移。
SAM(s)与rev(s)的后缀树的状态一一对应。
两者可以在
通过直观判断可知。详见附录5。具体证明这里省略。
4.3 与后缀数组的联系
不过好像SA能做的SAM都可以?因此并不想写这方面的东西。。
4.4 其他性质
这里讨论一些与应用相关的性质。
定理1:Right集合总是与其Parent子树中在主链上的状态有关。
推论11:Right集合大小等于其Parent树上子树中在主链上的状态数。
证明:(本证明引用了构造算法)显然只有主链上的状态才会扩充Right集合的元素种类数。因此证明本引理即证明非主链状态不会影响Right集合大小。非主链的状态即拆出的nq点,发现nq点相对于q,Right集合多出了L+1,而np的Parent为nq,Right(np)={L+1},即nq的Right集合实际上未新增Right集合元素。得证。
推论12:Right集合的最值与其Parent树上子树中在主链上的状态有关。
证明:由引理9可知,只有在主链上的状态才会增加元素种类,而新增的元素种类是已知的,即该状态的max值。又Right集合为子树中所有Right集合的并集,得证。
同时在维护SAM状态时,也要注意同时维护其Parent保持性质。
5 构造算法
前面一大篇的性质讨论,现在终于到算法层面了。
5.1 算法推演
考虑每次添加一个字符,维护所有原后缀。
设当前字符串为
考虑所有表示了
上次添加之后的
我们在添加了x之后,令
由Parent树,令
对于
由状态第3点可知,存在转移
以上是不存在冲突的转移,接下来考虑冲突的转移,考虑序列中第一个存在转移x的状态
否则我们就要考虑之前的字符对当前状态的影响了,如果我们强行加入
考虑拆解状态
发现
新建了状态
从算法的角度看来,状态数显然是线性的,每次添加字符,都会至多增加2个节点。
5.2 描述
令
新建
对
如果
如果存在,对p的第一个存在转移x的祖先p(原来的p没用了,这里重定义一下),令
否则建立状态
5.3 代码
void extend(char c) { int np = ++cnt, p = last; last = np; ma[np] = ++len; while (p && !trans[p][c]) trans[p][c] = np, p = fa[p]; if (!p) fa[np] = rt; else { int q = ch[p][c]; if (ma[p] + 1 == ma[q]) fa[np] = q; else { int nq = ++cnt; ma[nq] = ma[p] + 1; memcpy(trans[nq], trans[q], sizeof trans[q]); fa[nq] = fa[q]; fa[q] = fa[np] = nq; while (p && trans[p][c] == q) trans[p][c] = nq, p = fa[p]; } }}
不自带psmatrix根本没有耐心上图。。
建议看附录7一图流。
这里文字描述一下附录7的一图流的构造过程。。。不懂的可以辅助地看。。不过还是建议在草稿纸上跟着画比较好。
现在我们要构建aabbabd的SAM。
1. 首先有一个初始状态S,
2. 加入字符a,建立新状态1,上次的终态S没有转移,建立
3. 加入字符a,建立新状态2,上次的终态1没有转移,建立
4. 加入字符b,建立新状态3,上次的终态2以及Parent:1和S均没有转移b,建立转移到3,令3的Parent为S,
5. 加入字符b,建立新状态4,上次的终态3没有转移b,建立;3的后缀链接S存在转移b,而且
6. 加入字符a,建立新状态6,上次的终态4和其后缀连接5没有转移a,建立;5的后缀连接S存在转移a指向1,但是满足
7. 加入字符b,建立新状态7,上次的终态6没有转移b,建立;6的后缀链接1存在转移b到3,拆解3,建立新状态8,复制3的转移以及Parent,改3和7的Parent为8,将状态1的转移b改到状态8,
8. 加入字符d,建立新状态9,上次的终态7即其后缀链接8,5,S,建立转移d到9,
SAM构造完成。
6 应用
- 判断某串是否是s的子串。dfs一次SAM(s)即可。
- 不同子串的个数。由于Right集合的唯一性,因此所有子串在SAM中都是不重复的,因此求出SAM的路径条数即可。要注意的是所有节点都可作为路径的终点,因此dp:
d[v]=1+∑<u,v>∈E(SAM(s))d[u] - 求第k大子串,以字典序dfs自动机即可从小到大遍历子串,处理出每个状态向后延伸的子串数,O(len(S))遍历即可。
- 求某子串在原串中的出现次数。即等于其Right集合的大小,又Right集合的从属性,O(len(S))预处理出SAM(s)以及各状态的Right集合大小,询问即找到对应状态即可。
- 某子串第一次出现的位置。相当于求其Right集合的最小值,在构建SAM的时候顺带求出即可。
- 某子串所有出现位置,相当于求其Right集合的所有元素,沿Parent树回溯一次即可。
- 求最短不为s子串的字符串,发现不存在于SAM中的状态就不为其子串,因此dp:
d[u]=1+min<u,v>∈E(SAM(s))d[v] - 求2串的最长公共子串。fa(s)和fail指针。AC自动机的fail指针指向下一个具有最长公共后前缀的状态,而后缀自动机的Parent指针指向是与其拥有最长公共后缀的的某个状态(因为max(fa(s))=min(s)+1)。这一我们可以在串A的自动机上匹配B,找出A和B的最长公共子串。代码好像和AC自动机差不多?(POJ 2774, SPOJ 1811)
for(i=0;s[i];++i) { c=s[i]-'a'; while(p&&!trans[p][c])p=fa[p]; if(!p)p=rt,len=0; else len=min(len,ma[p])+1,p=trans[p][c];}
待续。。。。。
附录6似乎是一个论文的汉化,除了看起来有点烦躁之外挺好的(说得好像我这篇文章看起来就不烦躁似的)。
7 附录
相关博文
- http://cxjyxx.me/?p=244
- http://cxjyxx.me/?p=246
- http://lazycal.logdown.com/posts/195299-suffix-automaton-summary
- http://lazycal.logdown.com/posts/195354-postfix-automatically-building-tree-as-well-as-the-suffix-array
- http://blog.163.com/ps_lm/blog/static/20790406120125883433110/
- (实在找不到原文了) http://www.aiuxian.com/article/p-2504420.html
- (原文已被百度吞了,后缀自动机构造图流,建议看) http://cache.baiducontent.com/c?m=9d78d513d9d431dc4f9995697b13c0166a4381132ba1d50209d2843897732835506793ac57520770a0d13b275fa0131aacb22173441e3de7c595dd5dddccc36979d430340740d1070f9142a49717389260d601b8f14efaeca774c0f58c92c25750c154077983f7&p=c37fc54ad5c340ec0be29638177a9d&newp=9a57c64ad48917e007bd9b7d0d1794231610db2151d6d15f299bcc&user=baidu&fm=sc&query=%BA%F3%D7%BA%D7%D4%B6%AF%BB%FA&qid=fbaa635f00955bce&p1=10
- http://blog.sina.com.cn/s/blog_8fcd775901019mi4.html
- http://fanhq666.blog.163.com/blog/static/8194342620123352232937/
- http://www.xuebuyuan.com/1155017.html
- (好像混入了啥奇怪的东西) http://lazycal.logdown.com/posts/194613-netflow
- 后缀自动机题目小结
- 后缀自动机 小结
- 【字符串】后缀自动机小结
- 后缀自动机学习小结
- 后缀自动机刷题小结
- 后缀自动机小结 (spoj 8222)
- 后缀自动机
- 后缀自动机
- 后缀自动机
- 后缀自动机
- 后缀自动机)
- 后缀自动机
- 后缀自动机
- 后缀自动机
- 后缀自动机
- 后缀自动机
- 后缀自动机
- 后缀自动机
- Quartz之Cron表达式详解
- netlink
- oracle学习 第一章 简单的查询语句 ——02
- CSS3_边框
- OpenGL 基础图形绘制与投影变换
- 后缀自动机 小结
- 分享DCT,DST,Walsh,Hadamard,Haar和Slant图像处理程序
- Android学习中遇到的坑
- Maven常用仓库地址以及手动添加jar包到仓库
- Kafka源码分析之RecordAccumulator
- 多重背包代码
- Android学习之5.X过渡动画的实现
- LeetCode Valid Palindrom
- IF和SWITCH的原理