后缀自动机学习

本文详细介绍了后缀自动机的基本概念、构造算法及与后缀树的关系。后缀自动机是一种有限状态自动机,能够高效识别字符串的所有后缀。文章通过定义关键概念如状态、长度和转移等,阐述了后缀自动机的构建过程及其特性。
摘要由CSDN通过智能技术生成

定义

一个串 S 的后缀自动机是一个有限状态自动机,它可以且仅可以识别S的所有后缀,并且它拥有最少的状态。

后缀自动机的构造

一些记号

  • 母串 s ,标号为[0,|s|)
  • s[l,r] 表示母串的第 l 到第r个字符构成的子串
  • 从第 i 个位置开始的后缀记为sufi
  • 到第 i 个位置为止的前缀记为prefi
  • statestr 表示从初始状态读入 str 这个字符串到达的状态
  • rightstr 表示字符串 str s 中每一次出现的右端点集合
  • rightstate表示状态 state 代表的所有串出现的右端点集合
  • parent 树为 right 集合相互包含关系所构成的一棵树
  • lenstate 表示状态 state 所代表的最长的子串

性质

  • 不同状态的 right 集合要么不相交,要么是包含关系
    证明:设 r0rightArightB,states[l0,r0]=A,states[l1,r0]=B
    不失一般性地,令 l0<l1 ,于是 s[l1,r0] s[l0,r0] 的一个后缀,那么 A 状态能到达的状态,B集合必定能到达,所以 rightArightB
  • 每个状态 state 代表的所有不同的串在原串中出现的次数,恰好就是 rightstate 的大小。
    证明:由于状态之所以可以代表多个串是因为它们的 right 集合相同。一个串在母串中可以由长度 len 和它出现的右端点 r 确定。这里对于一个确定的串str,它的长度已经确定了。于是每一个右端点确定了一个它在母串的位置,于是这个串 str 在母串中出现的次数恰好就是 rightstr ,于是每个 state 代表的子串它在母串中出现的次数恰好就是 rightstate
  • statestr=A ,那么 A parent树上的父亲 faA 代表的所有串必定是 str 的后缀。
    证明:由于 stateAstatefaA ,利用类似于第一条性质的证明思路,容易得到。
  • 状态 state 所代表的所有串的长度是区间 (lenfastate,lenstate]
  • 一个状态 state 存在一个 char 的转移,那么 fastate 也存在一个 char 的转移
  • 任意一个 A 的后继状态B,必定满足 lenBlenA+1
    证明:设 A 所代表的最长的子串为str,那么存在转移 statestr+char=B ,于是 B 可以代表str+char这个子串。于是 B 状态所代表的所有子串的最小长度至少为|str|+1 lenA+1

后缀自动机的构造算法

首先空串的后缀自动机t是一个始状态 start lenstart 显然为 0
假设我们已经得到了串str的后缀自动机,我们想要构造出串 str+ch 的后缀自动机,其中 ch 是一个字符。
statestr+ch 显然不在 str 的后缀自动机中,新建一个状态 statestr+ch=end lenend 显然为 |str|+1
statestr=last ,那么 end 必定是 last 的一个后继状态,同理必定是 falast 的一个后继状态。
那么对于本来就不存在这个转移的节点我们就直接加入这个转移就可以了,对于存在这个转移的状态,我们需要在它的 right 集合中加入 |str|+1
假如 last 的所有祖先都不存在 char 这个转移,那么我们需要

  • end 的父亲设为始状态 start
  • lenend 设为 1

否则我们可以找到第一个存在这个转移的节点anc
anc 沿 char 转移的状态为 fail
那么 lenanc lenfail 可能有两种关系

  • lenanc+1=lenfail ,那么需要
    • fail 设为 end parent 树上的父亲
    • fail right 集合中加入 |str|+1
  • lenanc+1<lenfail ,显然对于长度属于 (l+1,lenfail] 部分的串是不可以加入 |str|+1 这个元素的。因此我们需要把这个状态 fail 分裂成两部分:加入了 |str|+1 这个元素的新状态 new 和没有加入的状态。那么这里需要
    • 新建一个状态 new
    • 将所有关于 fail 的转移都指向 new
    • new len 设为 lenanc+1
    • fail end 的父亲设为 new
    • new right 集合中加入 |str|+1

至此,新的自动机就被更新出来了。

right 集合的求法

实际上我们很多时候不需要求出 right 集合确切有哪些元素(如果真的需要就可能要做到 O(n2) ,或者用可持久化平衡树做到 O(nlogn) ),我们也许只需要知道 right 集合的大小之类的。那么由于有 parent 树这个特殊的结构,而且某个节点 v 的所有儿子节点的right集合构成了对 rightv 的一个划分。于是我们需要

  • 在所有的叶子节点,也就是母串 s 的所有前缀到达的状态,加入对应前缀。也就是说i[0,|s|),rightstateprefi+i
  • 对于所有的非叶子状态 v rightv=pchildvrightp

注意以上对集合的运算我用的加法其实是并。但是我们在计算同一个集合时,从来不会将同一个元素并入 2 次,所以简单地理解为加入就可以了。

而假如我们只需要求到right集合的大小,将以上所有对 right 集合的运算替换对 size 的运算就可以了。

后缀自动机与后缀树的联系

后缀自动机的 parent 树实际上是反串的后缀树。
假如我沿后缀自动机上跑到了一个状态 state parent 树上是一个叶子节点,那么这个串必定是某个前缀 prefx
我们不妨沿 state parent 树上往上跳,我们发现到达的每个状态都是 prefx 的一个后缀,这有点类似于 kmp fail 指针的意味(其实它的实际意义就是这样的。),和反串的后缀树相比:

  • 叶子节点的确是原串的一个前缀也就是反串的一个后缀
  • 任意一个节点的所有儿子节点的 LCA 就是该节点所代表的字符串

那么这显然是一棵 trie ,又因为它有最少的状态,所以我们就建出了反串的一棵后缀树。

那么具体地

  • 前缀 i 的状态是反串的后缀ni
  • 链接 state fastate 的边,对应原串长度为 lenstatelenfastate

这样子就建好了。


trie上构建后缀自动机的方法

由于这里的思路还没考虑好,只把构建方式贴出来好了。

  • 找出 trie bfs
  • 按照 bfs 序,用父节点的 state 作为末尾添加这个状态。

然后就完成了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值