本篇口胡写给我自己这样的东西都忘光的残废选手 以及那些刚学SAM,看了其他的一些东西并且没有完全懵逼的人

  (初学者还是去看有图的教程吧,虽然我的口胡没那么好懂,但是我觉得一些细节还是讲清楚了的)

  大概是重复一些有用的想法和性质,用以加深印象吧…如果可以的话希望也能理解得更透彻一点…

1、如何设计出一个后缀自动机?

  现在用的SAM并不是本来就在那里的,要比较深入地理解,就不能只从验证它对不对的角度考虑,而要考虑为什么它是这个样子。

  要一个能够接受后缀的有限状态机,并不用像现在的SAM那样弄,比如暴力建后缀Trie就可以做成一个满足要求的有限状态机…

  但是这不符合实际的需求,因为状态数和转移数都达到了$O(n^2)$

  所以考虑压缩一下…这里就有多种压缩的方向,从观察到后缀Trie上有大量冗长的没有分叉的链入手,把一段路径压缩,得到的就是后缀树(不过它已经不是我们要的自动机了)。

  另一个角度就是重新考虑一种构造法,放弃树的形态,使一个状态可以从多个输入串到达,为状态的减少提供了可能。

  当然,在分析之前,能做到什么程度是不知道的。现在我们开始分析。

  不考虑可行性,我们希望尽量少的状态和转移,当然是希望只有1个后缀状态了,但是当然不可行的。

  因为是每次添加一个字母进行转移,转移的过程中必须要在某个状态上,所以任意一个后缀的所有前缀也都必须存在转移,即所有子串必须存在转移。所以如果对所有子串建一个状态,就能得到一个状态数是$n^2$级的自动机了,这没有什么改善,还需要继续分析。

  一个Simple的想法是:发现有明显的可以合并的状态,对于一些后缀,从它们共同的尾巴上的某位置切开,得到一些它们的前缀,这些子串被我们分配了不同的状态,但是从它们到终态的转移是完全一样的,感觉可以合并成一个状态。于是就这么干吧,合并一下,哇,刚好只剩n个点了…但是定睛一看,真可惜,转移已经不是唯一的了,现在的情况变成了写开这个字符串,从初始状态能跳到任意一个位置(这里一个字符的转移就无法保证唯一了),然后接下去一步步转移…所以还是失败了。(直接合并后继相同的状态,会让从初始状态到达这个状态的转移途径被往前推,最终转移集中在初始点上,导致一个字符不得不转移到多个后继)

  但是明明感觉前面的分析没有问题,怎么就跪了呢…其实是因为我们合并的起点是$n*\left (n-1\right )$个状态。这意味着什么?这就是说可能在一开始,一个本质相同的子串因为出现的位置不同,被我们并入了不同的状态,于是如果输入串就是这个子串,那么就会同时达到不同的状态。而且我们也不能这样做完再合并本质相同的子串,因为后继状态会乱。

  于是我们知道,只能一开始就把这个问题先解决,再考虑优化状态数和转移数。所以我们第一步先合并本质相同的子串。

  接下来,再回顾我们一开始的想法,把后继相同的合并。不过这个时候有一点不同,一个子串对应的状态现在代表原串中不同位置出现的很多个它,它到终态的转移是所有这些出现位置的右端点后面的后缀。这些右端点也就是后缀自动机概念中的Right集啦。我们把Right集相同的状态合并起来,也就是把后继相同的状态合并。现在我们不用担心上面出现的不唯一的问题,因为所有输入串如果是一个子串,那么它能到达的只有一开始我们合并起来的那个状态。好的~这下也合法了,想做的优化也做了~那么就成功了吧…(等一下QwQ 现在和之前不一样了,现在的状态数和转移数有多少都不知道呢)

2、后缀自动机的状态数和转移数的线性性(参考cls当年的ppt)

  有了刚刚所说的Right集的定义,可以知道一些显然的性质…比如,给定一个串的Right集和串长,我们就能知道它是什么…(感觉好像废话…给了右端点和长度还能不知道么…)其实重要的是下面这句:

  一个Right集对应的串长是一个区间。

  显然如果长度$l$和$r$可以对应这个Right集,比$l$长的长度不会因为出现位置增加而变得不对应,比$r$短的长度不会因为出现位置减少而变得不对应,所以区间$\left [l,r\right ]$内的长度都合适。

  接下来还有一个性质。不妨把状态s的Right集对应才串长记作$\left [Min(s),Max(s)\right ]$,如果两个状态a、b的Right集相交于共同的右端点r,那么他们的长度区间不能相交,不然他们就会同时对应右端点为r的某些子串了,所以我们可以交换a、b使得$Max(a) < Min(b)$,那么每次状态b对应的任何子串出现的时候,状态a对应的任何子串都一定出现了,所以$Right(a)$包含$Right(b)$,又因为相同Right集对应的是相同的状态,所以$Right(a)$真包含$Right(b)$。这就是说:

  不同的状态的Right集只能是不相交或者真包含关系。

  这种性质很容易让人想到树对不对,按照真包含关系做成树的话,大于一个元素的集合必定有大于等于两个儿子(保证了不会出现无用的链),只有一个元素的n个集合就是叶节点,所以总节点数小于等于2n-1。喵呀,这样就说明状态数是线性的了。还差一点点,那就是转移数也是线性的。下面就来证明:

  取自动机的一个有向的生成树,使得初始状态出发可以到达所有状态。对于一条不在树上的边,我们可以在树上从初始状态走到它的起点,然后经过它并走到终态,走的这条路径对应的一定是原串的一个后缀。所以每条非树边一定可以对应某些后缀,而不同的两条非树边对应的后缀不可能有交,因为那样的话两条非树边同时成为了“路径上第一条非树边”,所以非树边数小于等于n。算上生成树上的边,我们得到:转移数小于等于2n-1

  呼 完成啦…

3、同时构造后缀自动机和后缀树

  弄了那么久,自动机有什么用啊…很多字符串的问题又不是判断是不是后缀就完事了的…SAM的图论性质只是一个DAG而已,只能做做DP而已,维护什么东西很困难呀…

  既然树形结构可以维护的东西很多,不妨把树用上吧…你说哪里有树?刚刚证明状态数线性的时候不就有一棵吗?而且这颗树的性质还是挺好的…建SAM的过程中就会用到它。

  把这棵树上的父亲称为pre,那么有$Min(s)-1=Max(pre_s)$,因为不断删左端的字符导致Right集增加的临界点就是$Min(s)-1$。同样,$Right(pre_s)$显然就是最小的刚好真包含$Right(s)$的Right集。

  这棵树叫做pre树或者parent树什么的…通过开始分析状态线性性的时候发现的那些性质,我们可以弄更多东西,比如说,一个子串的全部出现位置的相关信息…只要自底向上跑一次pre树就好了,要求出现位置的第k大什么的话都行,只要离线一下跑个线段树合并…不离线也行,跑出pre树的DFS序建主席树也可以求…

  如果要构造一个SAM,可以使用增量法,在已经建好的SAM上扩展一个字符x使之成为新串的SAM。

  首先 我们知道有一件事是必做的 那就是新建一个节点,接在原来的最后一个状态(把整个串作为输入到达的状态)后面,然后按照pre的性质,如果这个状态没有字符x的转移,显然可以直接加上到新点的x转移,然后再跳到它的pre继续这个操作。最后有两种情况,如果跳到了初始状态,那么新节点的pre就应该是初始状态了,通过上面的性质可以说明。

  另一种情况就是跳到某个点p已经有了x转移指向q(再往上跳也一定有x转移了),这时如果$Max(p)+1=Max(q)$的话是没有问题的,也容易知道新点的pre应该是q;

  但是如果$Max(p)+1<Max(q)$就有事情了,因为x加在$Right(p)$中的最长后缀后面得到的长度是$Max(p)+1$,而如果$Right(q)$里加入新末尾的话,大于$Max(p)+1$的长度的那些串将不再合法,所以不能把pre设成q…怎么办呢?可以以这个出事的长度为分界点,把$Right(q)$对应的长度区间拆开,变成两个长度区间,其中最大值小的一段是$\left [Min(q),Max(p)+1\right ]$,这两段区间对应的是两个Right集,也就是说会变成两个点,由前面的性质可以知道两个区间中最大值小的那个区间会成为最大值大的区间对应的点的pre,同时它们原来的转移是一致的(因为我们没有修改q的转移),现在转化为了$Max(p)+1=Max(q')$的情况,把新点的pre设成分裂出来的q'就好了。

  于是构造就这么轻易的完成啦…但是我觉得真正有趣的地方还是在于它另一个非常喵的性质:

  串str的后缀自动机的pre树就是串str的逆的后缀树

  真是无比奇喵呀,是为什么呢?观察到我们在pre树上往上跳的过程,其实就是把长度区间不断缩短,而本来已经存在的右端点接下来还是存在,所以其实就是把左端点往右缩,而且因为叶节点的Right集有1到n的每个元素,不断跳pre的过程中左端点是从1一直往右跳,直到跳过右端点时达到初始节点。所以其实这就是反串的后缀树,路径压缩也都压好了,边上的字符数也显然就是$Max(p)-Min(p)+1=Max(p)-Max(pre_p)$…所以我们就有了一个线性构造后缀树的简单方法~

  倒着从后往前依次插入原串的字符,这个过程等价于把串反过来,所以得到的就是原串的后缀树,不过还差一点,就是,后缀树需要得到边上的第一个字符…怎么弄呢?这种后缀树和后缀自动机的关系提供给了我们一个新的思路。比如说,原来我们做的分裂节点,如果用后缀树的观点看,就是在新加入一个后缀的过程中,需要在一条已经被压缩的路径上分叉,所以要新建一个点,把分叉的路径接上去(观察刚刚我们对pre做的操作,动手画一画就能够发现)。这种理解其实比后缀自动机的观点容易理解多了~用这种观点,我们可以非常简单地求出后缀树边上的第一个字符。

  倒着构造后缀自动机的过程中,对与后缀树做的操作是添加一条经过压缩的路径,代表原串的一个后缀。所以我们可以用$End(p)$表示后缀树的根到p这个点的路径对应的子串的结尾在原串的哪里。每次新加的点都是压缩过的原串后缀,所以它们的End都是n,而如果发生了分叉(后缀自动机上的分裂),那么因为边长是可以简单地求得的,所以我们可以计算出分叉点的End值。写出来的话就是:如果q分裂成$q_f$和$q_s$,且$q_f$是pre的话,$End(q_s)$不变,因为它不是分叉的那个,而$End(q_f)=End(q)-(Max(q)-Max(q_f))$。现在我们就可以通过End算出每条边的第一个字符,$pre_p$到$p$的边上的第一个字符就是原串的第$End(p)-(Max(p)-Max(pre_p))+1$个字符。

  好的…现在就神奇地完成了后缀自动机和后缀树的线性构造…比Ukk容易多了吧(因为我不会Ukk嘛)

  应用的话…现在已经变成后缀树了,比起正着建后缀自动机的pre树,它多了字典序功能(好像很多因为树结构带来的好处pre树也有)…以及DFS一下就可以建出后缀数组…嗯,还是比较强势的…

  大概我会的东西就那么多啦…Trie上的广义后缀自动机的论文并没有好好啃完…只是会写而已(不会证就来用是不是耍流氓233)…有机会新开一篇好了…

康复计划#1 再探后缀自动机&后缀树的更多相关文章

  1. 【BZOJ-1396&2865】识别子串&字符串识别 后缀自动机/后缀树组 + 线段树

    1396: 识别子串 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 312  Solved: 193[Submit][Status][Discuss] ...

  2. 洛谷P4493 [HAOI2018]字串覆盖(后缀自动机+线段树+倍增)

    题面 传送门 题解 字符串就硬是要和数据结构结合在一起么--\(loj\)上\(rk1\)好像码了\(10k\)的样子-- 我们设\(L=r-l+1\) 首先可以发现对于\(T\)串一定是从左到右,能 ...

  3. [模板] 后缀自动机&&后缀树

    后缀自动机 后缀自动机是一种确定性有限状态自动机, 它可以接收字符串\(s\)的所有后缀. 构造, 性质 翻译自毛子俄罗斯神仙的博客, 讲的很好 后缀自动机详解 - DZYO的博客 - CSDN博客 ...

  4. BZOJ3413: 匹配(后缀自动机 线段树合并)

    题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并... 首先可以转化一下模型(想不到qwq):问题可以转化为统计\(B\)中每个前缀在\(A\)中出现的次数.(画一画就出来了) 然后直 ...

  5. cf666E. Forensic Examination(广义后缀自动机 线段树合并)

    题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并 首先对所有的\(t_i\)建个广义后缀自动机,这样可以得到所有子串信息. 考虑把询问离线,然后把\(S\)拿到自动机上跑,同时维护一下 ...

  6. 洛谷P2178 [NOI2015]品酒大会(后缀自动机 线段树)

    题意 题目链接 Sol 说一个后缀自动机+线段树的无脑做法 首先建出SAM,然后对parent树进行dp,维护最大次大值,最小次小值 显然一个串能更新答案的区间是\([len_{fa_{x}} + 1 ...

  7. BZOJ1396: 识别子串(后缀自动机 线段树)

    题意 题目链接 Sol 后缀自动机+线段树 还是考虑通过每个前缀的后缀更新答案,首先出现次数只有一次,说明只有\(right\)集合大小为\(1\)的状态能对答案产生影响 设其结束位置为\(t\),代 ...

  8. [Luogu5161]WD与数列(后缀数组/后缀自动机+线段树合并)

    https://blog.csdn.net/WAautomaton/article/details/85057257 解法一:后缀数组 显然将原数组差分后答案就是所有不相交不相邻重复子串个数+n*(n ...

  9. luogu5212/bzoj2555 substring(后缀自动机+动态树)

    对字符串构建一个后缀自动机. 每次查询的就是在转移边上得到节点的parent树中后缀节点数量. 由于强制在线,可以用动态树维护后缀自动机parent树的子树和. 注意一个玄学的优化:每次在执行连边操作 ...

随机推荐

  1. hadoop+海量数据面试题汇总(二)

    何谓海量数据处理? 所谓海量数据处理,无非就是基于海量数据上的存储.处理.操作.何谓海量,就是数据量太大,所以导致要么是无法在较短时间内迅速解决,要么是数据太大,导致无法一次性装入内存. 那解决办法呢 ...

  2. STM32实现HID和u盘复合设备

      USB设备可以定义一个复合设备,复合设备分两种,一种是一个设备多个配置,还有一种是一个配置多个接口,在本例中采用一个配置多个接口的方式 首先修改设备描述符,标准设备描述符和报告描述符都不需要修改, ...

  3. webstrom 编码

    设置文件保存格式: webstrom的右下角选择你需要的编码

  4. adb报错:The connection to adb is down, and a severe&nbs

    我觉得这就像是adb的大姨妈一样,不过处理起来还是很easy滴:  1.报错:BUILD FAILEDD:\workspace\ganji\build.xml:144: The following e ...

  5. ceentos5.5 配置samba服务&用户&组

    准备 Change Root Password passwd root 在提示下建立新密码 静态IP vi /etc/sysconfig/network-scripts/ifcfg-eth0  #网络 ...

  6. 用mui框架开发手机app项目实践中的那些事儿

    http://www.yilingsj.com/xwzj/2015-04-29/260.html 最近在玩mui框架,坑的我是:西湖的水,全都是眼泪!!! 公司的手机app要进行改版,我率先想到的是j ...

  7. 安装MYSql Windows7下MySQL5.5.20免安装版的配置

    MySQL Windows安装包说明: 1.mysql-5.5.20-win32.msi:Windows 安装包,图形化的下一步下一步的安装. 2.mysql-5.5.20.zip,这个是window ...

  8. jQuery 获取和设置type为hidden的input的值

    HTML代码 <input type="hidden" name="type" id="type" value="1&quo ...

  9. Java 页面的工具包

    所谓工具包,是指把页面的功能划分出来,放到另外一个包里面.方面工程管理.结构清晰.团队协作等. 根据原来的例子:要做一个com.myweb包的工具包com.myweb.tool 为导航栏统一创建接口 ...

  10. HDFS存储系统

    HDFS存储系统 一.基本概念 1.NameNode HDFS采用Master/Slave架构.namenode就是HDFS的Master架构.主要负责HDFS文件系统的管理工作,具体包括:名称空间( ...