后缀数组(SA)总结
后缀数组(SA)总结
这个东西鸽了好久了,今天补一下
概念
后缀数组\(SA\)是什么东西?
它是记录一个字符串每个后缀的字典序的数组
\(sa[i]\):表示排名为\(i\)的后缀是哪一个。
\(rnk[i]\):可以理解为\(SA\)数组的逆,记录后缀\(i\)的排名是多少,\(rnk[SA[i]]=i\)。
\(lcp[i]\):别人一般叫\(height\),表示后缀\(SA[i]\)与\(SA[i-1]\)的最长公共前缀的长度。
后缀排序
求出后缀数组的算法,模板题
代码
先上代码,便于理解
#define cmp(i, j, k) (y[i] == y[j] && y[i + k] == y[j + k])
void Get_SA() {
static int x[MAX_N], y[MAX_N], bln[MAX_N];
int M = 122;
for (int i = 1; i <= N; i++) bln[x[i] = a[i]]++;
for (int i = 1; i <= M; i++) bln[i] += bln[i - 1];
for (int i = N; i >= 1; i--) sa[bln[x[i]]--] = i;
for (int k = 1; k <= N; k <<= 1) {
int p = 0;
for (int i = 0; i <= M; i++) y[i] = 0;
for (int i = N - k + 1; i <= N; i++) y[++p] = i;
for (int i = 1; i <= N; i++) if (sa[i] > k) y[++p] = sa[i] - k;
for (int i = 0; i <= M; i++) bln[i] = 0;
for (int i = 1; i <= N; i++) bln[x[y[i]]]++;
for (int i = 1; i <= M; i++) bln[i] += bln[i - 1];
for (int i = N; i >= 1; i--) sa[bln[x[y[i]]]--] = y[i];
swap(x, y); x[sa[1]] = p = 1;
for (int i = 2; i <= N; i++) x[sa[i]] = cmp(sa[i], sa[i - 1], k) ? p : ++p;
if (p >= N) break;
M = p;
}
}
算法流程
求\(sa\)的算法有倍增法和\(DC3\),因为后者有码量大、常数大、我不会等种种缺点,
这里只介绍倍增算法。
我们如果对于每个倍增完的二元组,每个都\(sort\)一下,复杂度是\(O(nlog^2)\)的。
那么将基数排序应用到其中去,就可以做到\(O(nlogn)\),具体做法:
我们考虑一下普通的基数排序是怎么排二元组
先将第二位丢进桶里,然后按照第一维的次序取出。
那么这个字符串怎么排呢?
首先当\(k=0\)时,我们直接桶排一下就行了。
但是我们还要接着排啊,
还记得吧,基排序是先按照第二维从小往大排
那么,我们就先把第二维的顺序搞出来
首先最小的一定就是没有第二维的东西
所以我们先把这些数直接丢进数组里面
接下来就是有第二维的东西啦
第\(i\)位的第二维是啥?\(rnk[i+k]\)
所以,从小到达枚举\(sa\),这样保证第二维从小往大
那么,只要\(sa[i]>k\)
就证明它是一个东西的第二维
所以,把\(sa[i]−k\)
丢到数组里面去就好啦
这样的话,按照第二维就排好啦
再来依次按照第一维丢到桶里面去
做一遍基数排序就好啦
这样就能够求出\(sa\)啦
看起来很简单诶。。
只是数组不要搞混了
一定搞清楚每个数组是干啥的
比如我的代码
\(sa\)是后缀数组,\(sa[i]\)表示排名为i的串是哪一个
\(rnk[i]\)相当于排名,\(rnk[i]\)表示第i个串的排名
\(x,y\)两个数组是记录顺序的
分别记录第一维和第二维的排序的顺序
\(bln\)是桶。
如果实在理解不了,就背吧,反正也没有多长
那么\(lcp\)数组怎么求呢?
取\(\forall i<j\),不妨设\(rnk[j]<rnk[k]\),那么以\(j\)开头的后缀和\(k\)开头的后缀的最长公共前缀就是\(\min _{i=rnk[j]+1}^{rnk[k]} lcp[i]\)。
有一个引理:
定义\(h[i]=lcp[rnk[i]]\),那么,\(h[i]\geq h[i-1]-1\)
证明:设\(s[k...]\)为排在\(s[i-1...]\)的前一名的后缀,其最长公共前缀为\(h[i-1]\),则\(s[k+1...]\)与\(s[i...]\)的最长公共前缀显然大于等于\(h[i-1]-1\),原结论得证。
然后这样求就可以了:
for (int i = 1; i <= N; i++) rnk[sa[i]] = i;
for (int i = 1, j = 0; i <= n; i++) {
if (j) j--;
while (a[i + j] == a[sa[rnk[i] - 1] + j]) ++j;
lcp[rnk[i]] = j;
}
一些trick
总结蒯了一些食用SA时的\(trick\):
一、对于可重复的最长重复子串问题(若子串\(s\)重复出现次数大于等于二,则称重复子串)\(Ans=\max_{i=1}^nlcp_i\)。
二、对于不可重叠的最长重复子串问题,二分,将问题转化为是否有两个长度为\(k\)的子串是相同的,且不重叠。将\(lcp\)数组分组,最长公共前缀不小于\(k\)的为一组其中如果有一组\(sa[i]\)之差大于\(k\)时,则成
立。
三、对于可重叠的重复\(k\)次最长重复子串,与上一种方法思路相似,二分,问题转化为判断是否存在\(k\)个长度为\(l\)的子串是相同的,将最长公共子串大于\(l\)的后缀分为一组,查看每一组内后缀个数是否大于\(k\)。
四、对于多个字符串的问题,通常用一个原串中不会出现的字符将两个字符串连接为一个。对于最长公共子串问题,首先将两个字符串用一个未出现过的字符连接起来,然后求出它们的最长公共前缀,解时注意判断是否在间隔符两边。
五、求取长度不小于\(k\)的公共子串个数时,将两个字符串按照上述方法连接,中间用一个未曾出现过的字符隔开,计算所有后缀之间最长公共前缀的长度,用单调栈维护最长公共前缀的长度。
六、对于在多个字符串中,出现不小于\(k\)个字符串的最长公共子串。按照上述方法连接多个字符串后,使用二分法。对于给定的长度,先分组,判断每组字符串后缀是否出现在不同的\(k\)个字符串中。
七、对于在每个字符串中至少出现两次且不重叠的最长公共子串时,按照上述方法连接多个字符串,使用二分法。对于给定的长度,先分组,判断是否有一组包含每个字符串中的两个不重叠答案。
一些后话
我还不太熟悉,题目暂未整理出来。
以后会提供每个\(trick\)的例题及一些题单。
如有错漏之处,请联系作者。
参考文章:
yyb的博客 https://www.cnblogs.com/cjyyb/p/8335194.html
清华大学出版社《ACM/ICPC算法基础训练教程》第8章
后缀数组(SA)总结的更多相关文章
- 后缀数组SA学习笔记
什么是后缀数组 后缀数组\(sa[i]\)表示字符串中字典序排名为\(i\)的后缀位置 \(rk[i]\)表示字符串中第\(i\)个后缀的字典序排名 举个例子: ababa a b a b a rk: ...
- 后缀数组SA入门(史上最晦涩难懂的讲解)
参考资料:victorique的博客(有一点锅无伤大雅,记得看评论区),$wzz$ 课件(快去$ftp$%%%),$oi-wiki$以及某个人的帮助(万分感谢!) 首先还是要说一句:我不知道为什么我这 ...
- bzoj3796(后缀数组)(SA四连)
bzoj3796Mushroom追妹纸 题目描述 Mushroom最近看上了一个漂亮妹纸.他选择一种非常经典的手段来表达自己的心意——写情书.考虑到自己的表达能力,Mushroom决定不手写情书.他从 ...
- [笔记]后缀数组SA
参考资料这次是真抄的: 1.后缀数组详解 2.后缀数组-学习笔记 3.后缀数组--处理字符串的有力工具 定义 \(SA\)排名为\(i\)的后缀的位置 \(rk\)位置为\(i\)的后缀的排名 \(t ...
- 【字符串】后缀数组SA
后缀数组 概念 实际上就是将一个字符串的所有后缀按照字典序排序 得到了两个数组 \(sa[i]\) 和 \(rk[i]\),其中 \(sa[i]\) 表示排名为 i 的后缀,\(rk[i]\) 表示后 ...
- 浅谈后缀数组SA
这篇博客不打算讲多么详细,网上关于后缀数组的blog比我讲的好多了,这一篇博客我是为自己加深印象写的. 给你们分享了那么多,容我自私一回吧~ 参考资料:这位dalao的blog 一.关于求Suffix ...
- 后缀数组SA
复杂度:O(nlogn) 注:从0到n-1 const int maxn=1e5; char s[maxn]; int sa[maxn],Rank[maxn],height[maxn],rmq[max ...
- 洛谷2408不同字串个数/SPOJ 694/705 (后缀数组SA)
真是一个三倍经验好题啊. 我们来观察这个题目,首先如果直接整体计算,怕是不太好计算. 首先,我们可以将每个子串都看成一个后缀的的前缀.那我们就可以考虑一个一个后缀来计算了. 为了方便起见,我们选择按照 ...
- 洛谷4248 AHOI2013差异 (后缀数组SA+单调栈)
补博客! 首先我们观察题目中给的那个求\(ans\)的方法,其实前两项没什么用处,直接\(for\)一遍就求得了 for (int i=1;i<=n;i++) ans=ans+i*(n-1); ...
随机推荐
- redis几种加锁的实现
1. redis加锁分类 redis能用的的加锁命令分表是INCR.SETNX.SET 2. 第一种锁命令INCR 这种加锁的思路是, key 不存在,那么 key 的值会先被初始化为 0 ,然后再执 ...
- CF 553E Kyoya and Train
题目分析 期望\(\text{dp}\). 设\(f_{i,j}\)表示在第\(j\)个时刻从\(i\)点出发,到达终点的期望花费. 有转移方程: \[ f_{x,t}=\min_{(x,y)\in ...
- [HEOI2012]朋友圈
题目 我们发现我们要求的是一个最大团问题,众所周知这是一个\(NP\)难问题,除了爆搜没有什么别的方法,但是这道题我们可以根据图的特殊性质入手 我们如果把\(B\)国的人分成奇数和偶数两类,就会发现奇 ...
- ubuntu16.04 Detectron目标检测库配置(包含GPU驱动,Cuda,Caffee2等配置梳理)
Detectron概述 Detectron是Facebook FAIR开源了的一个目标检测(Object Detection)平台. 用一幅图简单说明下Object Detection.如Mask R ...
- PAT——1057. 数零壹
给定一串长度不超过105的字符串,本题要求你将其中所有英文字母的序号(字母a-z对应序号1-26,不分大小写)相加,得到整数N,然后再分析一下N的二进制表示中有多少0.多少1.例如给定字符串“PAT ...
- 2018 HNUCM ACM集训队选拔第一场
1.小c的倍数问题 http://acm.hdu.edu.cn/showproblem.php?pid=6108 分析: 比赛的时候真的是各种想,结果发现自己是想多了...数论基础差得一批 求有多少个 ...
- 404 Note Found 队-Alpha10
目录 组员情况 组员1(组长):胡绪佩 组员2:胡青元 组员3:庄卉 组员4:家灿 组员5:凯琳 组员6:翟丹丹 组员7:何家伟 组员8:政演 组员9:黄鸿杰 组员10:刘一好 组员11:何宇恒 展示 ...
- 内核调试工具——strace
简介 strace常用来跟踪进程执行时的系统调用和所接收的信号. 在Linux世界,进程不能直接访问硬件设备,当进程需要访问硬件设备(比如读取磁盘文件,接收网络数据等等)时,必须由用户态模式切换至内核 ...
- pl/sql下载
详解Oracle客户端工具:PL/SQL工具下载: 下载地址:http://www.oraclejsq.com/article/010100114.html
- TCP中的三次握手和四次挥手
三次握手:目的是同步连接双方的序列号和确认号 并交换 TCP窗口大小信息. 理论上跟通话一样: a: 你听的到吗? b: 我能听到.只需要两次就可以了,但建立连接阶段不是双向即时通信的,且最终的目的 ...