后缀自动机的温习

觉得对于一个数据结构充分的学会使用,一定要对它的构成部分和定义概念有很充分很全面的了解.

所以我觉得的一种温习的最好方式就是:明晰概念->分析运用这样的层次.

概念的明晰确实是十分有用的,它既是理解他人算法的必要前提,也是你在看到题目后可以有创新想法的重要基础.

1.状态集合

  每个状态中存储的是一些right集合相同的字符串.初始态的right集合视为{1,2,3...n} [但是例如"aaa"中,"a"的right虽然也是{1,2,3}但是不与初始态重合,这个例子的parent树中初始态只有一个叶子节点].

  right相同的字符串的话,就不要觉得它们之间是没有联系的,它们应该是连续的是相互重叠的.

  比如说"abcdefdef中": "abcdef","bcdef","cdef" 就是放在一个right集合中的.

  状态中出现了一个属性mx,表示的是这个right集合中线段的最长长度.

  回顾构造sam的过程,mx的赋值是在实边的基础上赋值的.比如当末尾元素加入的时候mx[np]=mx[p]+1,因为当实边连接的时候表示前一个集合的某些位置可以往后向x的方向拓展一步,那么所有能拓展的子串中选最长的+1就是新的right集合中最长的子串了,而由于我们是沿着parent往上走,所以parent的前进相当于删去一个首字母得到原先串的一个后缀,所以最底下的串是最长的,所以也可以想象得到新增加的这个节点中的子串一定满足上面说的性质连续的.

  如果两个状态u,v,v--x-->u 且 mx[u]=mx[v]+1,则说明u中最长的串可以直接由v的最长串得来.v是所有能通过x到u的串中最长的,其它的串是它的后缀,且不是其他任何串包含.

2.状态之间的联系

  1.实边:

    如果在一个串的后面加上字符,那么right集合一定发生了改变.

    实边连接表示一个right集合的出现,这个right集合应该是前一个right集合中的部分位置往后+1得到的新right集合

    例如"abcdeabcdf"中R("abcd")={4,6}--e-->R("abcde")={5}

                       --f-->R("abcdf")={7}

    所以说如果有实边相连说明在串中的某个位置可以接着往后走这个元素到达新的一个状态.

    图像感受就是现在线段上你有很多个小点[表示当前状态的right集合]然后你可以在某些位置后面找到一个元素走一步,变成新的一些小点.

  2.parent边:

    每个状态中存下的是一段连续的串,其中最短的那个串去掉首字母就不在这个状态中了.那么就是到了parent节点中.parent(x)就是right集合包含x的所有中最小的集合.

    所以parent链往下相当于trans反过来的一个过程:首先你有一个线段上的小点,然后某些位置上你可以往前找到某一个字符,然后满足这个条件的某些小点就构成了parent挂下的right集合.

    比如下图中的边就表示的往前加入的一个字符。当然我们可以发现,加了字符之后能出现新right集合的条件就是你得有几种加法,比如"cd"可以加"b"或者"d";"bcd"可以加"a"或者"d"。

    这个的正确性比较显然...如果每个地方都只能加一样的...当然新串的right集合和自己这个相同咯...

    

    

    而反过来沿着parent链往上又是集合合并的过程。唔,这个的正确性你可以感受一下上面那个逆过程,想必是能理解的。

    当然我们在这里就发现了trans和parent的区别。

    1.trans是在原来串的基础上往后加字符,parent是往前加。所以一个是right集合中选一部分出来+1作为新的right,一个是直接从right集合中选一部分出来。

    2.trans是只要后面能加值就一定能产生新的状态,parent是必须要有至少两条不同的才能产生新的right集合。

3.状态之间的路径:

  1.实边构成的路径

    从初始态沿着实边到达某个状态的若干条路径即这个状态中所有的串.若某个状态指的是终止态,那么这是原串的一条后缀.

    任意两个状态之间的路径表示的是一个匹配的过程.还是回到图形上去.一开始你有一条线段上的若干个点,然后你沿着你需要匹配的链,从这些点中选出你需要的点并+1,然后再下一步再选出一些点,再+1...以此下去直到最  后你到达的那个状态.其中就是不断选择满足条件的right向后延伸的过程.也相当于一个匹配的过程.

  2.parent构成的链

    parent构成的链是向上的不断的去掉首字母的过程,其祖先都是它的一个后缀.parent树上不是向上的边构成的路径意义不是很大.

大概反思了一下所有的概念,然后就可以分析一些SAM处理的过程:

1.构造:

  构造是在串"S"所构成的SAM的基础上往后拓展一位c得到的SAM.由于SAM需要识别所有的子串,其中不包括c的子串已经得到,需要识别所有包括c的子串,也就是后缀.

  首先需要构造一个新的终止态np.因为终止态的最长串自然是整个串,所以mx[np]=mx[p]+1,然后因为是识别后缀,就应该是在原来的后缀基础上向后拓展c.找到所有后缀的方法很简单,首先找到了包括所有后缀的"S",然后沿着parent链往上就是一个不断取后缀的过程.

  对于一些原本没有连出c边的后缀,也就是它们在线段上的小点中没有一个位置能向后拓展一步c,现在它们在末尾可以拓展了,所以a[x][c]=np.

  对于某些后面原本连出c边的后缀"A",它们在线段上的小点中有某些位置往后走一步c,这样的话还要看是不是有包含了这个后缀而又不是原串后缀的串"B",如果有,那么这个后缀往后走一步c到达的状态的最长串就不是"Ac"而是"Bc",那么"Ac"的right集合会变化,但是"Bc"到"Ac"的这一段的right集合却不会变化.所以需要将这个状态分成两个部分,新的部分的right集合需要加入新的一个点.即fa[np]=nq,这个新的状态的最长长度就是"A"长度+1,即mx[nq]=mx[p]+1;同时也要包含以前的right集合,即fa[q]=nq.再看一下接着往上去的话,因为构成的是一棵parent树,"A"所在的状态相当于一个分叉点,再往上就不会分叉了.所以上面的right都会增加新的那个节点,即fa[nq]=fa[q].既然产生了新的,可能还会有别的后缀也扩展到"A"所扩展的状态去,所以需要把所有fa[x][c]==q的全部改成nq.当然如果满足了mx[q]==mx[p]+1就没有这么麻烦了,只需要给这个right集合扩充就行.fa[np]=q.然后整个过程就完成了.

2.寻找最长公共子串:

  首先给第一个串建立sam,然后让别的串在这个自动机上走.

  思考这个走的过程.现在我们在x状态,然后可以往后看是否具有一个当前字符的转移,如果有,就相当于把所有当前状态中可以转移的小点都往前走了一步.如果没有,那么就无法找到这么多匹配的,但是要利用已经匹配的信息,所以我们可以退回到当前串的后缀去看,然后把已经匹配的长度变成mx[S],相当于我们舍弃了这个状态到上一个状态的前面的部分,然后再去尝试往x的方向走.当然如果找了所有的,甚至到了初始态都没有x出边,那么就说明没有匹配,已匹配长度设置为0,指针指向初始态.

  当我们处于某个状态的时候,其实并不能判断我们匹配的是这个状态中的哪一个串,但是我们却知道我们匹配到了原串的哪几个位置.也就是知道下一个可以往哪个方向走.如果要知道匹配了多长则需要记录一个变量,但是如果是发现此处不能向后拓展后的沿着parent往回跳,那么就一定可以匹配到parent中的最长的那个串.

  寻找多个串的公共子串的时候,可以记录一下每个节点在每个位置匹配到的长度,然后对于所有的串在该位置上取一个最小值就是所有串公共的部分了.但是有要注意的地方就是如果你匹配到了某一个状态,那么它的parent中的所有的值其实你也可以匹配到,所以需要拓扑排序处理一下,把每个匹配到的值往上回溯更新,防止出现疏漏.

时间: 2024-10-26 14:05:09

后缀自动机的温习的相关文章

hiho一下第128周 后缀自动机二·重复旋律5

#1445 : 后缀自动机二·重复旋律5 时间限制:10000ms 单点时限:2000ms 内存限制:512MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一个音乐旋律被表示为一段数构成的数列. 现在小Hi想知道一部作品中出现了多少不同的旋律? 解题方法提示 输入 共一行,包含一个由小写字母构成的字符串.字符串长度不超过 1000000. 输出 一行一个整数,表示答案. 样例输入 aab 样例输出 5 解题方法提示 小Hi:本周的题目其实就是给定一个字符串S,要求出S的所有不同子串的数

后缀自动机总结

后缀自动机是一种确定性有限自动机(DFA),它可以且仅可以匹配一个给定串的任意后缀. 构造一个可以接受一个给定串的所有后缀的不确定性有限自动机(NFA)是很容易的,我们发现我们用通用的将NFA转换成对应DFA的算法转换出来的DFA的状态数都很小(O(n)级别的,远远达不到指数级别).于是,人们就开始研究这种特殊的NFA,并提出了在线增量算法,用O(n)的时间复杂度构造该NFA的DFA.在转换过程中,DFA中对应的NFA中的状态集合其实就是我们的right集合.——————以上在胡扯———————

BZOJ 2946 Poi2000 公共串 后缀自动机

题目大意:求n个串的最长公共子串 太久没写SAM了真是-- 将第一个串建成后缀自动机,用其它的串进去匹配 每个节点记录每个串在上面匹配的最大长度 那么这个节点对答案的贡献就是所有最大长度的最小值 对所有贡献取最大就行了= = 这最大最小看着真是别扭 #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define M 10100 using namesp

如何优雅的研究 RGSS3 番外(一) ruby 实现的后缀自动机

*我真的不会 ruby 呀* #encoding:utf-8 #============================================================================== # ■ Suffix_Automaton #------------------------------------------------------------------------------ # 后缀自动机. #============================

【BZOJ3926】[Zjoi2015]诸神眷顾的幻想乡 广义后缀自动机

[BZOJ3926][Zjoi2015]诸神眷顾的幻想乡 Description 幽香是全幻想乡里最受人欢迎的萌妹子,这天,是幽香的2600岁生日,无数幽香的粉丝到了幽香家门前的太阳花田上来为幽香庆祝生日. 粉丝们非常热情,自发组织表演了一系列节目给幽香看.幽香当然也非常高兴啦. 这时幽香发现了一件非常有趣的事情,太阳花田有n块空地.在过去,幽香为了方便,在这n块空地之间修建了n-1条边将它们连通起来.也就是说,这n块空地形成了一个树的结构. 有n个粉丝们来到了太阳花田上.为了表达对幽香生日的祝

【后缀自动机】【拓扑排序】【动态规划】hihocoder1457 后缀自动机四&#183;重复旋律7

解题方法提示 小Hi:我们已经学习了后缀自动机,今天我们再来看这道有意思的题. 小Ho:好!这道题目让我们求的是若干的数字串所有不同子串的和. 小Hi:你能不能结合后缀自动机的性质来思考如何解决本题? 小Ho:这道题目既然是关于子串,那么我知道从后缀自动机的所有状态中包含的子串的集合恰好对应原串的所有不重复子串. 小Hi:很好.那你可以先简化问题,想想只有一个串怎么做? 小Ho:好的.这个难不倒我.我上次已经知道如何计算一个串所有不同子串的数量,现在这题也类似,只不过计算更加复杂一点. 小Hi:

BZOJ3926 ZJOI2015 诸神眷顾的幻想乡 后缀自动机+DFS

题意:给定一颗字符树,求树中路径所构成的不同的字符串的数量,其中AB和BA视作不同的字符串 题解: 题目里有这样一句话:太阳花田的结构比较特殊,只与一个空地相邻的空地数量不超过20个. 一共有10W个点,却只有20个叶子……因此树上所有的字串就是以叶子为起点搜索出的所有字串,丽洁姐真的好善良啊- -(无雾) 这样从每个点开始就能跑出来一颗Trie树,对Trie构造广义后缀自动机——每个节点看成是一个根,在后面加字符的时候和普通的SAM一样. 然后在SAM上用DFS统计不同字串的数量即可 #inc

SPOJ 1812 Longest Common Substring II(后缀自动机)

[题目链接] http://www.spoj.com/problems/LCS2/ [题目大意] 求n个串的最长公共子串 [题解] 对一个串建立后缀自动机,剩余的串在上面跑,保存匹配每个状态的最小值, 取最小值中的最大值即可.由于跑的地方只记录了匹配结尾的状态, 所以还需要更新parent树上的状态,既然匹配到了子节点, 那么parent树链上的值就都能够取到l, 一开始给每个不同状态按照l从小到大分配储存地址, 这样,我们就可以从匹配长度最长的开始更新parent树的情况. [代码] #inc

hdu5853 (后缀自动机)

Problem Jong Hyok and String 题目大意 给你n个字符串,有q个询问. 定义set(s)={(i,j)} 表示 s在第i个字符串中出现,且末尾位置为j. 对于一个询问,求set(Qi)=set(t) ,t的数量. (n,q<=10^5 , 字符串总长<=10^5) 解题分析 直接将n个串塞进一个后缀自动机里面. 对于一个询问串qi,找到其在后缀自动机中出现的位置j. 则答案为len[j] - len[fail[j]] . (具体为什么还需要好好斟酌一下) 参考程序 1