后缀自动机(SAM)速成手册!

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

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

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

<font face="黑体" color="#ff00ff" size=5> SAM彻底能够当成黑盒来用!!!!!! </font>数据结构

因此我打算写一篇SAM速成博客。。。保证即学即会!学习

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

<font face="黑体" color="#00a0ff" size=5>Warning:想完全理解后缀自动机吗?那你有好消息了!请当即关闭此页面,在百度里搜索“后缀自动机 陈立杰”,开始愉快的学习吧!</font>(讲真,陈老师的ppt是讲的最好的,别的博客无能出其右者)spa

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

另外,除起点外的每一个节点还拥有一个**“后缀连接“**,记做$fa(x)$。后缀连接组成了一棵树,别的性质在构建完以后再讲。code

存储SAM利用的是相似于Trie树的存储结构,即便用$ch[][26]$数组保存状态转移的边。htm

知道了这些,构建SAM的工做就能够开始了。blog

开始建造后缀自动机

准备工做:创建数组$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的一些基本且经常使用的性质。

<font size=5 color=#7777ff>请牢记如下每一条内容!都十分有用!不要去问“为何是这样的”!(若是必定要问,请参照上文蓝色放大的Warning)</font>

首先,SAM的点数与边数都是$O(n)$的。<font color=#00cc00>记住,因为每次插入最多新建两个点,因此应该开字符总量两倍的空间。</font>计算空间时别忘了你开了26倍的$ch$数组。

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

起点能够看作是表明空串的点。

重点:定义子串的$right$集合:这个子串在原串中全部出现的位置的右端点的集合。

好比说:<font color=#00aaff>AA</font><font color=#ff0000>AAB</font><font color=#00aaff>BAAA</font><font color=#ff0000>AAB</font><font color=#00aaFF>A</font><font color=#ff0000>AAB</font><font color=#00aaff>BAA</font>

子串<font color=#ff0000>AAB</font>出现了3次,右端点集合为${5,12,16}$。这就是子串<font color=#ff0000>AAB</font>的$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!

原文出处:https://www.cnblogs.com/sclbgw7/p/10197629.html

相关文章
相关标签/搜索