后缀数组(SA)、后缀自动机(SAM)、广义 SAM 总结
后缀数组(SA)、后缀自动机(SAM)、广义 SAM 总结
后缀数组(SA)
基础知识
\qquad 后缀数组中涉及到的主要有三个重要的数组:1、 s a sa sa 数组: s a i sa_i sai 表示排名为 i i i 的后缀是哪一个后缀;2、 r a n k rank rank 数组: r a n k i rank_i ranki 表示第 i i i 个后缀的排名是几;3、 h e i g h t height height 数组: h e i g h t i height_i heighti 表示排名为 i i i 的后缀与排名为 i − 1 i-1 i−1 的后缀的最长公共前缀( L C P LCP LCP)的长度。
\qquad 求后缀数组一般用的是时间复杂度 O ( n log n ) O(n\log n) O(nlogn),但是常数极小的倍增法。整体思想是基于基数排序的思想。大体过程为:1、先以第一关键字(长度为 1 1 1 的前缀)为基准排序;2、将原先的第一关键字作为现在的第二关键字,将长度为 2 2 2 的前缀作为第一关键字排序;3、重复上述步骤,每次第一关键字的长度倍增,直到排序结束停止。
\qquad 代码实现上有点小技巧,比如:第一次排序结束后,再进行第二次排序前要先求出所有后缀的第二关键字排名。在求第二关键字排名的时候,我们可以发现:第 i i i 个后缀的第一关键字刚好是第 i − 1 i-1 i−1 个后缀的第二关键字。在后面排序进行次数更多的时候也是同理:假设进行了 k k k 次排序,那么此时第 i i i 个后缀的第一关键字刚好是第 i − 2 k i-2^k i−2k 个后缀的第二关键字。
\qquad 对于第一关键字相同的后缀,我们要按照第二关键字来排序。但是怎么做到呢?这里有个小技巧就是倒序枚举第二关键字的排名。这样就可以在第一关键字相同的情况下第二关键字也有序。
\qquad C o d e : Code: Code:
\qquad 关于 H e i g h t Height Height 数组的求法,这里先引出几条性质:1、排名为 i i i 的后缀与排名为 j j j 的后缀( i < j i<j i<j)的 L C P LCP LCP 等于 min \min min( L C P LCP LCP(排名为 i i i 的后缀,排名为 i + 1 i+1 i+1 的后缀), L C P LCP LCP(排名为 i + 1 i+1 i+1 的后缀,排名为 i + 2 i+2 i+2 的后缀), … \dots …)。形式化地, L C P ( s a i , s a j ) = min ( L C P ( s a i , s a i + 1 ) , L C P ( s a i + 1 , s a i + 2 ) , … ) LCP(sa_i,sa_j)=\min(LCP(sa_i, sa_{i+1}), LCP(sa_{i+1}, sa_{i+2}),\dots) LCP(sai,saj)=min(LCP(sai,sai+1),LCP(sai+1,sai+2),…)。2、记 h i = L C P ( i , s a r k i − 1 ) h_i=LCP(i,sa_{rk_i-1}) hi=LCP(i,sarki−1),即 h i = h e i g h t r k i h_i=height_{rk_i} hi=heightrki则 h i ≥ h i − 1 − 1 h_i\geq h_{i-1}-1 hi≥hi−1−1。根据第二条性质,我们便可以通过求解 h h h 数组来求 h e i g h t height height 数组。求 h h h 数组就暴力拓展即可,时间复杂度 O ( n ) O(n) O(n)。
\qquad C o d e : Code: Code:
例题
【模板】后缀排序
\qquad 题面
\qquad 大板。
[NOI2015] 品酒大会
\qquad 题面
\qquad h e i g h t height height 数组的应用。
\qquad 两杯酒 p , q p,q p,q 是 r r r 相似的,意味着以 p p p 开头的后缀和以 q q q 开头的后缀的 L C P LCP LCP 为 r r r。而他们既然是 r r r 相似,也一定是 r − 1 r-1 r−1 相似, r − 2 r-2 r−2 相似……所以我们考虑倒着求解,先求解 n n n 相似,在求解 n − 1 n-1 n−1 相似……假设当前求解到 r r r 相似,那么我们将所有 h e i g h t i = r height_i=r heighti=r 的 i i i 与 i − 1 i-1 i−1 合并(注意这里指的是排名),用并查集维护,顺便维护最大值、次大值、最小值、次小值(两个负数相乘)即可。
\qquad 核心 C o d e : Code: Code:
基因突变
\qquad 简要题面已经给的很裸了,求出 h e i g h t height height 数组后用 s t st st 表维护下区间最值即可。
[AHOI2013] 差异
\qquad 题面
\qquad 一眼套路题,求出 h e i g h t height height 数组后转化为区间最小值求和,单调栈搞一搞即可。
\qquad 核心 C o d e : Code: Code:
不同子串个数
\qquad 题面
\qquad 典题。我们考虑正难则反。对于长度为 n n n 的字符串,一共有 n × ( n + 1 ) 2 \frac{n\times (n+1)}{2} 2n×(n+1) 个子串,这些子串都可以看成某个后缀的某个前缀。现在,我们考虑有哪些子串被重复统计了。注意到,若 h e i g h t i = j height_i=j heighti=j,那就意味着排名为 i i i 的后缀与排名为 i − 1 i-1 i−1 的后缀有着长度为 j j j 的公共前缀,那这 j j j 个长度分别为 1 , 2 , … , j 1,2,\dots,j 1,2,…,j 的子串就会被统计两次。所以,我们得出结论:本质不同的子串个数等于 n × ( n + 1 ) 2 − ∑ i = 2 n h e i g h t i \frac{n\times (n+1)}{2}-\sum_{i=2}^nheight_i 2n×(n+1)−∑i=2nheighti。
后缀自动机(SAM)
基础知识
\qquad 一个字符串的后缀自动机可以使用点数为 2 n 2n 2n 级、边数为 3 n 3n 3n 级的代价存储这个字符串的所有子串。后缀自动机上任意一条从根节点出发的路径都代表了原串的一个子串。
\qquad 相关概念: e n d p o s ( T ) endpos(T) endpos(T):在字符串 S S S 中, T T T 的所有结束位置构成的集合记作 e n d p o s ( T ) endpos(T) endpos(T)。 f a ( x ) fa(x) fa(x):将 x x x 这个状态所对应的所有字符串中,长度最短的字符串去掉首字母后所在的状态记作 f a ( x ) fa(x) fa(x)。后缀自动机上所有节点的 f a fa fa 共同构成一棵树,叫做 p a r e n t t r e e parent\;tree parenttree。
\qquad 构建过程理解起来挺抽象,不过代码很好写。
例题
【模板】后缀自动机(SAM)
\qquad 题面
\qquad 后缀自动机 p a r e n t t r e e parent\;tree parenttree 的经典应用:查找某一字串出现的次数。首先有显然的结论:一个状态对应的所有子串出现的次数相同;再一个结论:记 f x f_x fx 表示状态 x x x 表示的所有子串的出现次数,则 f x = ∑ f a v = x f v f_x=\sum_{fa_v=x}f_v fx=∑fav=xfv,感性理解很简单: x x x 的儿子出现了 f v f_v fv 次,那 x x x 一定也出现了 f v f_v fv 次。建树后统计即可。
\qquad 核心 C o d e : Code: Code:
[JSOI2012] 玄武密码
\qquad 题面
\qquad 建立 S S S 的后缀自动机,对于每个 T T T,暴力跑后缀自动机看能匹配多长即可。
\qquad 核心 C o d e : Code: Code:
最长公共子串
\qquad 先建立第一个串的后缀自动机,对于后面的串,暴力在自动机上跑,若失配则跳 f a fa fa 指针,每跳到一个节点更新一下匹配的长度并储存在当前节点处(同一个串取 m a x max max)。一个串跳完后,将所有结点储存的匹配长度存到 a n s ans ans 数组中(不同串取 m i n min min)。在所有串跳完后,输出 a n s ans ans 数组最大值即可。
\qquad 注意:一个串跳完后,要将自动机上储存的 m a x max max 值在 p a r e n t t r e e parent\;tree parenttree 上自下而上传递贡献。因为每个点记录的长度一定是当前节点表示的字符串的公共后缀,而 f a fa fa 储存的子串也是当前节点的后缀。
\qquad 核心 C o d e : Code: Code:
后缀自动机 2 重复旋律
\qquad 后缀自动机求本质不同子串个数。有两种方法:1、建反图后拓扑排序求路径条数;2、每次插入新字符的时候动态更新,插入一个字符带来的贡献是 l e n ( n p ) − l e n ( f a ( n p ) ) len(np)-len(fa(np)) len(np)−len(fa(np))。
\qquad 核心 C o d e : Code: Code:
后缀自动机 6 重复旋律
\qquad 先正常求每个状态对应的字符串出现几次,因为每个状态对应的字符串的长度是一段连续区间,所以线段树维护一下,区间取 max \max max 即可。
\qquad 核心 C o d e : Code: Code:
[TJOI2015] 弦论
\qquad 题面
\qquad 求第 K K K 小子串,建出后缀自动机后跑一跑 d p dp dp 即可,有点细节。
\qquad 核心 C o d e : Code: Code:
[SDOI2016] 生成魔咒
\qquad 题面
\qquad 动态维护本质不同字串个数板子题。
重复的旋律7
\qquad 建立广义 S A M SAM SAM 后跑拓扑 d p dp dp 即可。
广义 SAM
\qquad 广义 S A M SAM SAM 是将多个串建立在一个后缀自动机上,用于处理多串问题。
\qquad 广义 S A M SAM SAM 有两种伪实现方式:1、在相邻两串之间加特殊字符;2、每次加新串之前让 l s t = 1 lst=1 lst=1。这两种实现方式正确性是毋庸置疑的,不过效率较低。真正的广义 S A M SAM SAM 是先建立所有串的 T r i e Trie Trie 树,再在 T r i e Trie Trie 的基础上建立 S A M SAM SAM。
[HAOI2016] 找相同字符
\qquad 题面
\qquad 正难则反,先建立广义 S A M SAM SAM 求答案,再分别减去两个串单独建立 S A M SAM SAM 的答案。
串
\qquad 建立广义 S A M SAM SAM 后,在 p a r e n t t r e e parent\;tree parenttree 上自上而下传递合法子串的个数。可以将统计答案挂在 S A M SAM SAM 的节点上。
[ZJOI2015] 诸神眷顾的幻想乡
\qquad 题面
\qquad 遍历叶子节点作为根节点,建立广义 S A M SAM SAM,然后统计本质不同子串个数即可。
\qquad 核心 C o d e : Code: Code:
[NOI2018] 你的名字
\qquad 题面
\qquad 考虑对 S S S 和每个 T T T 均建立 S A M SAM SAM,我们每次并不需要知道 S S S 的 S A M SAM SAM 上哪些节点对应了 [ l , r ] [l,r] [l,r] 这段区间,只需要知道 [ l , r ] [l,r] [l,r] 的 e n d p o s endpos endpos 集合即可。用可持久化线段树合并维护区间 e n d p o s endpos endpos 集合。细节很多。
\qquad Code
[八省联考 2018] 制胡窜
\qquad 题面
\qquad 绝世好题,讲不了一点。
\qquad Code
__EOF__

本文链接:https://www.cnblogs.com/best-brain/p/18006544.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具