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


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

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

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

    SAM完全可以当成黑盒来用!!!!!!

    所以我打算写一篇SAM速成博客。。。保证即学即会!

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

    Warning:想彻底理解后缀自动机吗?那你有好消息了!请立即关闭此页面,在百度里搜索“后缀自动机 陈立杰”,开始愉快的学习吧!(讲真,陈老师的ppt是讲的最好的,别的博客无能出其右者)

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

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

    存储SAM利用的是类似于Trie树的存储结构,即使用(ch[][26])数组保存状态转移的边。

    知道了这些,构建SAM的工作就可以开始了。

    开始建造后缀自动机

    准备工作:建立数组(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的一些基本且常用的性质。

    请牢记以下每一条内容!都十分有用!不要去问“为什么是这样的”!(如果一定要问,请参照上文蓝色放大的Warning)

    首先,SAM的点数与边数都是(O(n))的。记住,由于每次插入最多新建两个点,所以应该开字符总量两倍的空间。计算空间时别忘了你开了26倍的(ch)数组。

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

    起点可以看做是代表空串的点。

    重点:定义子串的(right)集合:这个子串在原串中所有出现的位置的右端点的集合。

    比如说:AAAABBAAAAABAAABBAA

    子串AAB出现了3次,右端点集合为({5,12,16})。这就是子串AAB(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!

  • 相关阅读:
    Sql Server 2008卸载后再次安装一直报错
    listbox 报错 Cannot have multiple items selected when the SelectionMode is Single.
    Sql Server 2008修改Sa密码
    学习正则表达式
    Sql Server 查询第30条数据到第40条记录数
    Sql Server 复制表
    Sql 常见面试题
    Sql Server 简单查询 异步服务器更新语句
    jQuery stop()用法以及案例展示
    CSS3打造不断旋转的CD封面
  • 原文地址:https://www.cnblogs.com/sclbgw7/p/10197629.html
Copyright © 2020-2023  润新知