为了账号安全,请及时绑定邮箱和手机立即绑定

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

标签:
深度学习

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

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

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

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

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

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

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

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

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

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

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

开始建造后缀自动机

准备工作:建立数组ch,fa,lench,fa,len,准备指针last,cntlast,cnt。SAM的构造方法是不断地向已经建好的SAM中加入新的节点。lastlast表示上一个被插入的节点,cntcnt表示SAM中的节点数量。一开始,last=cnt=1last=cnt=1,表示只有一个起点的初始SAM。

接下来,假设要往SAM里加入一个字符xx。

  1. 新建节点np=++cntnp=++cnt。新建节点pp。p=lastp=last。last=nplast=np。

  2. 如果不存在ch[p][x]ch[p][x],令ch[p][x]=np,p=fa[p]ch[p][x]=np,p=fa[p]。重复此步骤。

  3. 如果到最后还没有一个pp拥有儿子xx,令fa[np]=1fa[np]=1。退出过程。

  4. 当ch[p][x]ch[p][x]出现时,令q=ch[p][x]q=ch[p][x]。如果len[q]==len[p]+1len[q]==len[p]+1,令fa[np]=qfa[np]=q。退出过程。

  5. 否则有点麻烦。新建节点nq=++cntnq=++cnt,将qq的儿子都复制给nqnq,令len[nq]=len[p]+1len[nq]=len[p]+1。

  6. 令fa[nq]=fa[q],fa[q]=fa[np]=nqfa[nq]=fa[q],fa[q]=fa[np]=nq。

  7. 从pp开始沿着后缀链接,将所有ch[p][x]==qch[p][x]==q的节点的ch[p][x]ch[p][x]都替换成nqnq。

将你的字符串的所有字符都一一进行如上操作后,你就得到了用你的字符串构建出来的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)O(n)的。记住,由于每次插入最多新建两个点,所以应该开字符总量两倍的空间。计算空间时别忘了你开了26倍的chch数组。

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

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

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

比如说:AAAABBAAAAABAAABBAA

子串AAB出现了3次,右端点集合为{5,12,16}{5,12,16}。这就是子串AAB的rightright集合。

一个节点能够代表的所有子串的rightright集合是一样的。rightright集合相等的子串一定被同一个节点代表。(所以,我们会使用“节点的rightright集合”这个说法。)两个节点的rightright集合之间要么真包含,要么没有交集。若节点yy的rightright集合包含了节点xx的rightright集合,那么yy能代表的子串均为xx能代表的子串的真后缀。

重点:定义节点xx的后缀链接fa(x)fa(x):如果有一些节点的rightright集合包含了xx的rightright集合,fa(x)fa(x)是其中rightright集合的大小最小的那一个。

后缀链接们组成了一棵“后缀链接树”(不是后缀树)。后缀链接树的根为起点。若节点yy的rightright集合包含了节点xx的rightright集合,那么yy在后缀链接树上是xx的祖先。

一个节点的rightright集合等于他在后缀链接树上的所有儿子的rightright集合的并集。而且儿子的rightright集合之间两两没有交集。

每个节点能代表的子串的长度范围是一段连续的区间。这很好理解,因为它们的结束位置都是相同的。

我们求出每个节点能代表的最长串的长度(即len(x)len(x))了,那最短长度呢?其实就等于后缀父亲节点的len+1len+1。也就是说,所有本质不同的子串的数量等于∑len(x)−len(fa(x))∑len(x)−len(fa(x))。

总结

以上就是SAM的基本性质~对于一道特定的题,你可能需要通过上面的性质推出你需要的新性质。如果你还有什么疑问可以向我留言,我(在退役前)会在一天之内回复的!(你也可以去问更强的boshi和litble,别去问zyf因为他已经退役了。)

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



点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消