SAM学习笔记

前(fei)言(hua):

咳咳,既然是学习笔记,那肯定是边学边写的啊,所以会持续更新呀。

SAM是啥子玩意?(在大佬讲之前完全听都没听过,但是根据PPT的排布以及讲的是字符串主题来看,这应该是“后缀自动机”了(本蒟蒻听过后缀自动机,仅仅只是听过,但是SAM这个名字就没听过了))

然后我百度了一下SAM,搜出来这玩意......

噫!好!我人傻了!当场爆粗口来了句氧化钙。

然后我耐心(气急败坏)的查了查“后缀自动机”|,终于正常了。

正题:

后缀自动机 (suffix automaton, SAM) 是一个能解决许多字符串相关问题的有力的数据结构(”高级算法“)。

后缀自动机中几个重要的概念:

1.endpos(S)表示的是子串S在原串中出现的位置(末位置),例如在ababa中,endpos(ab)={2,4}(好像也有称为right集合的,不管了),这玩意是个等价集

2.如果存在子串T以及S使得endpos(T)=endpos(S),那么我们则将S以及T归为同一类中,endpos相等的子串归在一起称为一个"状态",

我们给各个"状态"依次编号,那么就会得到若干个状态,在这里我们约定我们记为Str(x)表示状态x的所有字符串(x是状态的编号)

同样拿上面的例子来说事,endpos(ab)={2,4},endpos(b)={2,4},这样我们将"ab"以及"b"归为一类,称为一个"状态"

3.后缀链接,这玩意下文详细讲

4.parent树,这玩意下文详细讲

后缀自动机的几个性质

先看一组endpos的数据:

当原串为:“abbabaa”的时候

状态1.   空串                                      endpos={1,2,3,4,5,6,7}

状态2.  a                                             endpos={1,4,6,7}

状态3.  ab                                          endpos={2,5}

状态4. b                               endpos={2,3,5}

状态5.abb,bb                        endpos={3}

状态6.abba,bba                  endpos={4}

状态7.ba                                                                                 endpos={4,6}

状态8.abbab,bbab,bab                 endpos={5}

状态9.abbaba,bbaba,baba,aba           endpos={6}

状态10.abbabaa,bbabaa,babaa,abaa,baa,aa          endpos={7}

话说,泥萌有没有发现什么规律。

对于每一个状态,里面每一个串的长度都是逐一递减的,而且后一个串就是前一个串去掉第一个字符后得到的后缀

那么性质一来了:

如果endpos(S)=endpos(T),那么S为T的后缀或者T为S的后缀

不妨令|S|<|T|,那么S一定是T的后缀

证明:如果S不是T的后缀,那么S的第1到len(S)的字符中必然有一位与对应的T的第len(T)-len(S)+1到len(T)位不同

因为endpos(S)=endpos(T),显然与以上假设矛盾,那么不可能有S的第1到len(S)的字符中必然有一位与对应的T的第len(T)-len(S)+1到len(T)位不同

所以如果endpos(S)=endpos(T),那么S为T的后缀或者T为S的后缀.

性质二:

观察得到,如果S为T的后缀,则endpos(S)包含集合endpos(T)

如果S不是T的后缀,则endpos(S)∩endpos(T)=∅

性质三(转移函数)**:

挺重要的。

对于一个状态X,我们在属于它的串后面加上一个字符ch,只要这个字符的是属于由      endpos(状态X中的字符串)后一个的字符组成的集合nxt,

那么就满足产生的新串都属于同一个状态(这里可能我表述不大清楚,多给几个例子,方便理解)

例如上面的例子:

对于开篇的串的状态6,endpos()={4},那么nxt集合等于{"b"}

那么定然abba+b,bba+b属于一个同一个状态

对于串“abdabe”中

某个状态为: ab,b             endpos={2,5}

那么ab+d与b+d一定是属于同一类,

ab+e 与 b+e一定是同一类。

对于串"acbabacba"中

某个状态为:acba,cba       endpos={4,9}

那么对acba+b 与 cba+b一定是同一类的

 那么我们将会得到一个函数:f(S,c)=str(S)+(c∈nxt),S为状态号,nxt可以通过endpos求得,也就是s[endpos(str(S))+1]所得到的字符集

这个函数将会用来进行匹配以及构建SAM!!!!

性质四:

 前面说了“对于每一个状态,里面每一个串的长度都是逐一递减的,而且后一个串就是前一个串去掉第一个字符后得到的后缀”,那么我们观察得到每个状态都是“不完全的”

所谓的不完全就是无法直接从状态中的最长串一直延续到空串

例如“状态9.abbaba,bbaba,baba,aba           endpos={6}”

如果它是完全的,那么就会有接下来的:

“状态9.abbaba,bbaba,baba,aba,ba,a           endpos={6}”,

显然a与ba的endpos不与前面几项endpos相同对不对?所以它们不是同一个状态

我们要是强行将状态9完善呢?那么就会需要将“ba”以及“a”接进来,那么就需要一条链,这条链为:

状态9----->状态7------>状态2----->状态1

这条链肯定是唯一性的(不会有一模一样的两条链),并且被指向的那个状态所包含的字符串一定是当前状态     所有*(一定是的,因为串中字符串的长度是严格递减的)     字符串的后缀。

同时这些链它们的终点一定会空集,也就是状态1,所以,把状态1看成根节点的话,若干条这样的链也就构成了一棵树,也就是大名鼎鼎的parent树,我们惊奇的发现我们要的后缀自动机的节点就是parent树的节点!

bb了这么多(我太菜了,因为是边理解边写的,可能会有些误差,希望大佬们能够指出),现在终于到了讲构造后缀自动机的时候了(激动的小手准备开始打板子了)!

还是先讲一下构造的思路(只贴代码不讲思路的博客都是耍流氓):

 后缀自动机的构造是在线的,我们通过每次插入节点的方式来实现(有之前手写二叉堆内味了)

    • last 为添加字符 c 之前,整个字符串对应的状态(一开始我们设 last=0 ,算法的最后一步更新 last )。
    • 创建一个新的状态 cur,并将 len(cur) 赋值为 len(last)+1 ,在这时 link(cur) 的值还未知。
    • 现在我们按以下流程进行(从状态 last 开始)。如果还没有到字符 c的转移,我们就添加一个到状态 cur 的转移,遍历后缀链接。如果在某个点已经存在到字符 c 的转移,我们就停下来,并将这个状态标记为p 。
    • 如果没有找到这样的状态 p ,我们就到达了虚拟状态 1 ,我们将 link(cur) 赋值为 0并退出。
    • 假设现在我们找到了一个状态 p ,其可以通过字符 c 转移。我们将转移到的状态标记为 qqq 。
    • 现在我们分类讨论两种状态,要么 len(p)+1=len(q) ,要么不是。
    • 如果 len(p)+1=len(q),我们只要将 link(cur) 赋值为 q 并退出。
    • 否则就会有些复杂。需要 复制 状态 q :我们创建一个新的状态 clone ,复制 q的除了 len 的值以外的所有信息(后缀链接和转移)。我们将len(clone) 赋值为 len(p)+1 。
      复制之后,我们将后缀链接从 cur 指向clone ,也从 q 指向 clone 。
      最终我们需要使用后缀链接从状态 p 往回走,只要存在一条通过 ppp 到状态 q 的转移,就将该转移重定向到状态 clone 。
    • 以上三种情况,在完成这个过程之后,我们将 last 的值更新为状态cur 。

以上带点内容借鉴(抄袭)于:[ HatsuneMiku神佬的题解](https://www.luogu.com.cn/problem/solution/P3804)

代码就先咕咕了。。。。。

鸣谢:sh妹,Rothen,涛队,万总帮助小蒟蒻MYCui

By MYCui
原文地址:https://www.cnblogs.com/MYCui/p/13523142.html