Pro Tanto Quid Retribuamus.|

Schucking_Sattin

园龄:2年11个月粉丝:13关注:28

📂String
🔖notes
2022-08-02 17:34阅读: 479评论: 0推荐: 0

SAM (后缀自动机)

SAM

Intro

Use

Definition

字符串 s 的 SAM 是一个接受 s 的所有后缀的最小 DFA (确定性有限自动机或确定性有限状态自动机)。

  • SAM 是一张 DAG。节点被称为 状态,边被称作状态间的 转移

  • 图存在一个源点 t0,称作 初始状态,其它各节点均可从 t0 出发到达。

  • 每个 转移 都标有一些字母。从一个节点出发的 转移 均不同。

  • 存在一个或多个 终止状态。如果我们从 t0 出发,最终转移到了一个终止状态,则路径上的所有转移连接起来一定是 s 的一个后缀。

    s 的每个后缀均可用一条从 t0 到某个终止状态的路径构成。

  • 在所有满足上述条件的自动机中,SAM 的结点数是最少的。

Demonstration

endpos (结束位置)

Definition

endpos

对于 s 的任意非空子串 t,我们记 endpos(t) 为在 st 的所有结束位置。

规定字符串从 0 开始编号。

endpos() 是一个集合。

等价类

两个子串 t1t2endpos 集合可能相等:endpos(t1)=endpos(t2)

这样所有 s 的非空子串都可以根据它们的 endpos 被分成若干 等价类

Property

Lemma 1

Lemma 1 (endpos)

字符串 s 的两个非空子串 uw (假设 |u||w|)的 endpos 相同,当且仅当字符串 us 中的每次出现,都是以 w 的后缀的形式存在的。

Lemma 2

Lemma 2 (endpos)

考虑两个非空子串 uw(假设 |u||w|)。则:

{endpos(w)endpos(u)if u is a suffix of wendpos(w)endpos(u)=otherwise

Lemma 3

Lemma 3 (endpos)

考虑一个 endpos 等价类,将类中所有子串按长度非递增的顺序排序。每个子串都不会比它前一个子串长,与此同时每个子串也是它前一个子串的后缀。换句话说,对于同一等价类的任一两子串,较短者为较长者的后缀,且该等价类中的子串长度恰好覆盖整个区间 [x,y]

其中 x 表示最短子串长度,y 表示最长子串长度。

Intro

考察 SAM 中某个不是 t0 的状态 v

v 对应着 一个 等价类,设 w 是这个等价类中长度最长的子串。

w 的后缀按长度降序排列,则前面一些后缀和 w 同属一个等价类,而另一些后缀对应另外的 endpos

并且至少有一个 w 的后缀对应另外的 endpos(考虑空串)。

x 为满足不属于在包含 w 的等价类里的一个 w 的后缀,则状态 v 连向状态 x

状态的本质就是重要的转折子串。

Property

Lemma 4

Lemma 4 (link)

所有后缀链接构成一棵根节点为 t0 的树。

Lemma 5

Lemma 5 (link)

通过 endpos 集合构造的树(每个子节点的子集都包含在父节点的子集中)与通过后缀链接构成的树相同。

Demonstration

Algorithm

SAM 是一个 在线 算法。

大致方法是:逐个加入 字符,对应维护 SAM。

一开始 SAM 只包含一个状态 t0,编号为 0(其它状态的编号为 1,2,)。为了方便,对于状态 t0 我们指定 len=0link=11 表示虚拟状态)。

现在,任务转化为实现给当前字符串添加一个字符 c 的过程。算法流程如下:

  • last 为添加字符 c 之前,整个字符串对应的状态(一开始我们设 last=0,算法的最后一步更新 last)。
  • 创建一个新的状态 cur,并将 len(cur) 赋值为 len(last)+1,在这时 link(cur) 的值还未知。
  • 现在我们按以下流程进行(从状态 last 开始)。如果还没有到字符 c 的转移,我们就添加一个到状态 cur 的转移,遍历后缀链接。如果在某个点已经存在到字符 c 的转移,我们就停下来,并将这个状态标记为 p
  • 如果没有找到这样的状态 p,我们就到达了虚拟状态 t0,我们将 link(cur) 赋值为 0 并退出。
  • 假设现在我们找到了一个状态 p,其可以通过字符 c 转移。我们将转移到的状态标记为 q
  • 现在我们分类讨论两种状态,要么 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 往回走,只要存在一条通过 p 到状态 q 的转移,就将该转移 重定向 到状态 clone
  • 以上三种情况,在完成这个过程之后,我们将 last 的值更新为状态 cur

上面是贺的 OI-wiki 的讲解。没有图怎么行呢?!

Demonstration

之前那些图是贺的,接下来我亲自画。假设要对 s=#bcbc 建立 SAM。

下面这张图有点小问题:3 号节点有两个后缀链接。

就是对于 q 这样本身就有后缀链接的节点,在重定向时要把原来的后缀链接去掉。

实际操作很简单嘛,就是赋值。

最后是完整的 SAM:

一个下午.past. QWQ 手残+强迫症

本文作者:Schucking-Sattin

本文链接:https://www.cnblogs.com/Schucking-Sattin/p/16544548.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Schucking_Sattin  阅读(479)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起