后缀数组简介
一些约定
字符串相关的定义请参考 字符串基础。
字符串下标从
字符串
" 后缀
后缀数组是什么?
后缀数组(Suffix Array)主要关系到两个数组:
其中,
这两个数组满足性质:
解释
后缀数组示例:
后缀数组怎么求?
O(n^2logn) 做法
我相信这个做法大家还是能自己想到的:将盛有全部后缀字符串的数组进行 sort
排序,由于排序进行
O(nlog^2n) 做法
这个做法要用到倍增的思想。
首先对字符串
倍增过程:
-
用两个长度为
的子串的排名,即 和 ,作为排序的第一第二关键字,就可以对字符串 的每个长度为 的子串: 进行排序,得到 和 ; -
之后用两个长度为
的子串的排名,即 和 ,作为排序的第一第二关键字,就可以对字符串 的每个长度为 的子串: 进行排序,得到 和 ; -
以此倍增,用长度为
的子串的排名,即 和 ,作为排序的第一第二关键字,就可以对字符串 的每个长度为 的子串 进行排序,得到 和 。其中,类似字母序排序规则,当 时, 视为无穷小; -
即是子串 的排名,这样当 时,得到的编号数组 ,也就是我们需要的后缀数组。
过程
倍增排序示意图:
显然倍增的过程是 sort
对子串进行排序是
除此之外,每次倍增在 sort
排序完后,还有额外的
所以这个算法的时间复杂度就是
O(nlogn) 做法
在刚刚的
由于计算后缀数组的过程中排序的关键字是排名,值域为
一些常数优化
如果你把上面那份代码交到 LOJ #111: 后缀排序 上:
这是因为,上面那份代码的常数的确很大。
第二关键字无需计数排序
思考一下第二关键字排序的实质,其实就是把超出字符串范围(即
优化计数排序的值域
每次对
将 rk[id[i]] 存下来,减少不连续内存访问
这个优化在数据范围较大时效果非常明显。
用函数 cmp 来计算是否重复
同样是减少不连续内存访问,在数据范围较大时效果比较明显。
把 oldrk[sa[i]] == oldrk[sa[i - 1]] && oldrk[sa[i] + w] == oldrk[sa[i - 1] + w]
替换成 cmp(sa[i], sa[i - 1], w)
,
bool cmp(int x, int y, int w) { return oldrk[x] == oldrk[y] && oldrk[x + w] == oldrk[y + w]; }
。
若排名都不相同可直接生成后缀数组
考虑新的
O(n) 做法
在一般的题目中,常数较小的倍增求后缀数组是完全够用的,求后缀数组以外的部分也经常有
但如果遇到特殊题目、时限较紧的题目,或者是你想追求更短的用时,就需要学习
SA-IS
可以参考 诱导排序与 SA-IS 算法,另外它的 评论页面 也有参考价值。
DC3
可以参考[2009]后缀数组——处理字符串的有力工具 by. 罗穗骞。
后缀数组的应用
寻找最小的循环移动位置
将字符串
例题:「JSOI2007」字符加密。
在字符串中找子串
任务是在线地在主串
从字符串首尾取字符最小化字典序
题意:给你一个字符串,每次从首或尾取一个字符组成字符串,问所有能够组成的字符串中字典序最小的一个。
暴力做法就是每次最坏
由于需要在原串后缀与反串后缀构成的集合内比较大小,可以将反串拼接在原串后,并在中间加上一个没出现过的字符(如 #
,代码中可以直接使用空字符),求后缀数组,即可
height 数组
LCP(最长公共前缀)
两个字符串
下文中以
height 数组的定义
O(n) 求 height 数组需要的一个引理
当
当
根据
既然后缀
那么不妨用
那么后缀
进一步地,后缀
因为后缀
所以
于是就可以得出
O(n) 求 height 数组的代码实现
利用上面这个引理暴力求即可:
height 数组的应用
两子串最长公共前缀
感性理解:如果
严格证明可以参考[2004]后缀数组 by. 徐智磊。
有了这个定理,求两子串最长公共前缀就转化为了 RMQ 问题。
比较一个字符串的两个子串的大小关系
假设需要比较的是
若
否则,
不同子串的数目
子串就是后缀的前缀,所以可以枚举每个后缀,计算前缀总数,再减掉重复。
“前缀总数”其实就是子串个数,为
如果按后缀排序的顺序枚举后缀,每次新增的子串就是除了与上一个后缀的 LCP 剩下的前缀。这些前缀一定是新增的,否则会破坏
所以答案为:
出现至少 k 次的子串的最大长度
出现至少
所以,求出每相邻
可以使用单调队列
是否有某字符串在文本串中至少不重叠地出现了两次
可以二分目标串的长度
连续的若干个相同子串
我们可以枚举连续串的长度
例题:「NOI2016」优秀的拆分。
结合并查集
某些题目求解时要求你将后缀数组划分成若干个连续 LCP 长度大于等于某一值的段,亦即将
经典题目:「NOI2015」品酒大会
结合线段树
某些题目让你求满足条件的前若干个数,而这些数又在后缀排序中的一个区间内。这时我们可以用归并排序的性质来合并两个结点的信息,利用线段树维护和查询区间答案。
结合单调栈
例题:「AHOI2013」差异
被加数的前两项很好处理,为
我们知道
考虑每个位置对答案的贡献是哪些后缀的 LCP,其实就是从它开始向左若干个连续的
单调栈部分类似于 Luogu P2659 美丽的序列 以及 悬线法。
类似的题目:「HAOI2016」找相同字符。
习题
- Uva 760 - DNA Sequencing
- Uva 1223 - Editor
- Codechef - Tandem
- Codechef - Substrings and Repetitions
- Codechef - Entangled Strings
- Codeforces - Martian Strings
- Codeforces - Little Elephant and Strings
- SPOJ - Ada and Terramorphing
- SPOJ - Ada and Substring
- UVA - 1227 - The longest constant gene
- SPOJ - Longest Common Substring
- UVA 11512 - GATTACA
- LA 7502 - Suffixes and Palindromes
- GYM - Por Costel and the Censorship Committee
- UVA 1254 - Top 10
- UVA 12191 - File Recover
- UVA 12206 - Stammering Aliens
- Codechef - Jarvis and LCP
- LA 3943 - Liking's Letter
- UVA 11107 - Life Forms
- UVA 12974 - Exquisite Strings
- UVA 10526 - Intellectual Property
- UVA 12338 - Anti-Rhyme Pairs
- DevSkills Reconstructing Blue Print of Life
- UVA 12191 - File Recover
- SPOJ - Suffix Array
- LA 4513 - Stammering Aliens
- SPOJ - LCS2
- Codeforces - Fake News (hard)
- SPOJ - Longest Commong Substring
- SPOJ - Lexicographical Substring Search
- Codeforces - Forbidden Indices
- Codeforces - Tricky and Clever Password
- LA 6856 - Circle of digits
参考资料
本页面中(4070a9b 引入的部分)主要译自博文 Суффиксный массив 与其英文翻译版 Suffix Array。其中俄文版版权协议为 Public Domain + Leave a Link;英文版版权协议为 CC-BY-SA 4.0。
论文:
贡献者:@Imple@Menci@Jimmy@minghu6@Nanarikom@WenzelTian@Ir1d@Baoshuo@HelloEarly@kenlig@hly@Clouder@mgt@Chrogeek@Mauve@billchenchina@countercurrent_time@Henry-ZHR@Haoshen@ouuan@雷蒻@orzcyand1317@XLor@ksyx@SY@Chro@pretend-fal
本页面最近更新:2/3/2023, 12:00:00 AM,更新历史
发现错误?想一起完善? 在 GitHub 上编辑此页!
本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用