后缀自动机学习

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

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

定义

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

后缀自动机的构造

一些记号

  • 母串s,标号为[0,|s|)
  • s[l,r]表示母串的第l到第r个字符构成的子串
  • 从第i个位置开始的后缀记为sufi
  • 到第i个位置为止的前缀记为prefi
  • statestr表示从初始状态读入str这个字符串到达的状态
  • rightstr表示字符串strs中每一次出现的右端点集合
  • 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,那么Aparent树上的父亲faA代表的所有串必定是str的后缀。
    证明:由于stateAstatefaA,利用类似于第一条性质的证明思路,容易得到。
  • 状态state所代表的所有串的长度是区间(lenfastate,lenstate]
  • 一个状态state存在一个char的转移,那么fastate也存在一个char的转移
  • 任意一个A的后继状态B,必定满足lenBlenA+1
    证明:设A所代表的最长的子串为str,那么存在转移statestr+char=B,于是B可以代表str+char这个子串。于是B状态所代表的所有子串的最小长度至少为|str|+1lenA+1

后缀自动机的构造算法

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

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

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

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

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

right集合的求法

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

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

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

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

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

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

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

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

那么具体地

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

这样子就建好了。


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

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

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

然后就完成了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值