后缀自动机速成

后缀自动机速成

概述

后缀自动机(suffix automaton,SAM)是一个能解决许多字符串相关问题的有力的数据结构。
举个例子,以下的字符串问题都可以在线性时间内通过 SAM 解决。

  • 在另一个字符串中搜索一个字符串的所有出现位置。
  • 计算给定的字符串中有多少个不同的子串。

直观上,字符串的 SAM 可以理解为给定字符串的 所有子串 的压缩形式。值得注意的事实是,SAM将所有的这些信息以高度压缩的形式储存。对于一个长度为 n 的字符串,它的空间复杂度仅为 O(n) 。此外,构造 SAM 的时间复杂度仅为 O(n) 。准确地说,一个 SAM 最多有 2n1 个节点和 3n4 条转移边。(摘抄自 OI-Wiki)

性质

性质1:SAM,其实就是一个 DAG,边权为一个字符,从这个图的起始节点开始的任意一条路径,都是字符串的一个子串,并且不会出现有不同的路径代表同一个子串,也就是从开始节点开始的任意一条路径都代表着唯一的一个子串。

性质2:从原点到任意一个结点的路径,可能有多条,在这些路径中,他们的长度是连续的,并且长度短的是长度长的后缀。

性质3:对于走到一个节点的任意路径所形成的子串(也就是性质2的所有子串),他们在原串中出现的次数是一样的。

结束位置 endpos

考虑字符串 s 的任意非空子串 t ,我们记 endpos(t) 为在字符串 st 的所有结束位置(假设对字符串中字符的编号从零开始)。例如,对于字符串 abcbc ,我们有 endpos(bc)=2,4

两个子串 t1t2endpos 集合可能相等: endpos(t1)=endpos(t2) 。这样所有字符串 s 的非空子串都可以根据它们的 endpos 集合被分为若干等价类。

考虑 SAM 中某个不是 t0 的状态 v 。我们已经知道,状态 v 对应于具有相同 endpos 的等价类。我们如果定义 w 为这些字符串中最长的一个,则所有其它的字符串都是 w 的后缀。

我们还知道字符串 w 的前几个后缀(按长度降序考虑)全部包含于这个等价类,且所有其它后缀
(至少有一个一一空后缀)在其它的等价类中。我们记 t 为最长的这样的后缀,然后将 v 的后缀链接连到 t 上。

换句话说,一个 后缀链接 link(v) 连接到对应于 w 的最长后缀的另一个 endpos 等价类的状态。

通过后缀链接构成 一颗树,称 后缀链接树。

以下是对字符串 abcbc 构造 SAM 时产生的后缀链接树的一个例子,节点被标记为对应等价类中最长的子串。

另一个解释:一个结点的fail指针,就指向这个结点的路径形成的最短子串去掉第一个字母所形成的字符串的结点。

应用

下面我们来看一些可以用 SAM 解决的问题。简单起见,假设字符集的大小 k 为常数。这允许我们认为增加一个字符和遍历的复杂度为常数。

检查字符串是否出现

给一个文本串 T 和多个模式串 P ,我们要检查字符串 P 是否作为 T 的一个子串出现。

我们在 O(|T|) 的时间内对文本串 T 构造后缀自动机。为了检查模式串 P 是否在 T 中出现,我们沿转移(边)从 t0 开始根据 P 的字符进行转移。如果在某个点无法转移下去,则模式串 P 不是 T 的一个子串。如果我们能够这样处理完整个字符串 P ,那么模式串在 T 中出现过。

对于每个字符串 P ,算法的时间复杂度为 O(|P|) 。此外,这个算法还找到了模式串 P 在文本串中出现的最大前缀长度。

不同子串个数

给一个字符串 S ,计算不同子串的个数。

对字符串 S 构造后缀自动机。
每个 S 的子串都相当于自动机中的一些路径。因此不同子串的个数等于自动机中以 t0 为起点的不同路径的条数。

考虑到 SAM 为有向无环图,不同路径的条数可以通过动态规划计算。

所有不同子串的总长度

给定一个字符串 S ,计算所有不同子串的总长度。

与上一题大体类似,动态规划解决即可。

字典序第 k 大子串

给定一个字符串 S 。多组询问,每组询问给定一个数 Ki ,查询 S 的所有子串中字典序第 Ki 大的子串。

解决这个问题的思路可以从解决前两个问题的思路发展而来。字典序第 k 大的子串对应于 SAM 中字典序第 k 大的路径,因此在计算每个状态的路径数后,我们可以很容易地从 SAM 的根开始找到第 k 大的路径。

预处理的时间复杂度为 O(|S|) ,单次查询的复杂度为 O(|ans||Σ|)(其中 ans 是查询的答案, |Σ| 为字符集的大小)。

出现次数

那么,我们可以通过对原串不同前缀的子串所到达的结点都赋值为1,然后再通过fail指针递归,不断给这些前缀的父串加上他们的出现次数(因为该串出现,其父串一定出现)

例题:NSUBSTR - Substrings

考虑对于一个点 u ,其状态对应所有字符串的长度都在 [lenfau+1,lenu] 间,且连续。
容易在后缀链接树上 DP 求出子串出现次数。于是我们用线段树做区间取 max,单点查询即可。复杂度 O(nlogn)

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