后缀数组C++详解
后缀定义
“后缀i”代表以第i个字符开头的后缀,存储是用i代表字符串s的后缀s[i...n]
后缀数组是什么?
后缀数组(Suffix Array)主要关系到两个数组:sa 和 rk。
其中,sa[i] 表示将所有后缀排序后第 i 小的后缀的编号,也是所说的后缀数组,后文也称编号数组 sa;
rk[i] 表示后缀 i 的排名,是重要的辅助数组,后文也称排名数组 rk。
这两个数组满足性质:sa[rk[i]]=rk[sa[i]]=i。
解释
后缀数组示例:

后缀数组怎么求?
O(n^2logn) 做法
我相信这个做法大家还是能自己想到的:将盛有全部后缀字符串的数组进行 sort 排序,由于排序进行 O(n\log n) 次字符串比较,每次字符串比较要 O(n) 次字符比较,所以这个排序是 O(n^2\log n) 的时间复杂度。
O(nlog^2n) 做法
这个做法要用到倍增的思想。
首先对字符串 s 的所有长度为 1 的子串,即每个字符进行排序,得到排序后的编号数组 sa_1 和排名数组 rk_1。
倍增过程:
用两个长度为 1 的子串的排名,即 \(rk_1[i]\) 和 \(rk_1[i+1]\),作为排序的第一第二关键字,就可以对字符串 s 的每个长度为 2 的子串:\(\{s[i\dots \min(i+1, n)]\ |\ i \in [1,\ n]\}\) 进行排序,得到 sa_2 和 rk_2;
之后用两个长度为 2 的子串的排名,即 rk_2[i] 和 rk_2[i+2],作为排序的第一第二关键字,就可以对字符串 s 的每个长度为 4 的子串:\(\{s[i\dots \min(i+3, n)]\ |\ i \in [1,\ n]\}\) 进行排序,得到 sa_4 和 rk_4;
以此倍增,用长度为 w/2 的子串的排名,即 \(rk_{w/2}[i]\) 和 \(rk_{w/2}[i+w/2]\),作为排序的第一第二关键字,就可以对字符串 s 的每个长度为 w 的子串 \(s[i\dots \min(i+w-1,\ n)]\) 进行排序,得到 sa_w 和 rk_w。其中,类似字母序排序规则,当 i+w>n 时,\(rk_w[i+w]\) 视为无穷小;
\(rk_w[i]\) 即是子串 \(s[i\dots i + w - 1]\) 的排名,这样当 w \geqslant n 时,得到的编号数组 sa_w,也就是我们需要的后缀数组。
#include <bits/stdc++.h>
using namespace std;
const int N = 1000010;
char s[N];
int n, w, sa[N], rk[N << 1], oldrk[N << 1];
// 为了防止访问 rk[i+w] 导致数组越界,开两倍数组。
// 当然也可以在访问前判断是否越界,但直接开两倍数组方便一些。
int main() {
int i, p;
scanf("%s", s + 1);
n = strlen(s + 1);
for (i = 1; i <= n; ++i)
sa[i] = i, rk[i] = s[i];
for (w = 1; w < n; w <<= 1) {
sort(sa + 1, sa + n + 1, [](int x, int y) {
return rk[x] == rk[y] ? rk[x + w] < rk[y + w] : rk[x] < rk[y];
}); // 这里用到了 lambda
memcpy(oldrk, rk, sizeof(rk));
// 由于计算 rk 的时候原来的 rk 会被覆盖,要先复制一份
for (p = 0, i = 1; i <= n; ++i) {
if (oldrk[sa[i]] == oldrk[sa[i - 1]] &&
oldrk[sa[i] + w] == oldrk[sa[i - 1] + w]) {
rk[sa[i]] = p;
} else {
rk[sa[i]] = ++p;
} // 若两个子串相同,它们对应的 rk 也需要相同,所以要去重
}
}
for (i = 1; i <= n; ++i)
printf("%d ", sa[i]);
return 0;
}
后缀数组C++详解的更多相关文章
- js数组方法详解
Array对象的方法-25个 /*js数组方法详解 */ /* * 1 concat() 用于连接多个数组或者值-------------- * 2 copyWithin() 方法用于从数组的指定位置 ...
- JavaScript数组方法详解
JavaScript数组方法详解 JavaScript中数组的方法种类众多,在ES3-ES7不同版本时期都有新方法:并且数组的方法还有原型方法和从object继承的方法,这里我们只介绍数组在每个版本中 ...
- 最新java数组的详解
java中HashMap详解 http://alex09.iteye.com/blog/539545 总结: 1.就像引用类型的数组一样,当我们把 Java 对象放入数组之时,并不是真正的把 Java ...
- JS数组映射详解
现在这里占个坑位,免的忘了,需要整理一下最近的内容: 1.数组映射的使用 2.微信分享功能详解 3.jq自己封装 4.HTML的富文本应用
- [转载]Splay Tree数组实现+详解
变量声明:f[i]表示i的父结点,ch[i][0]表示i的左儿子,ch[i][1]表示i的右儿子,key[i]表示i的关键字(即结点i代表的那个数字),cnt[i]表示i结点的关键字出现的次数(相当于 ...
- JavaScript Array数组方法详解
Array类型是ECMAScript中最常用的引用类型.ECMAScript中的数据与其它大多数语言中的数组有着相当大的区别.虽然ECMAScript中的数据与其它语言中的数组一样都是数据的有序列表, ...
- [转]Java数组初始化详解
一维数组1) int[] a; //声明,没有初始化 2) int[] a=new int[5]; //初始化为默认值,int型为0 3) int[] a={1,2,3,4,5}; ...
- javascript数组常用方法详解
1,splice(). array.splice(index,many,list1,list2....) 参数1.index位置 负数为从结尾处算,倒数第一为-1:参数2,many要删除的项目, ...
- iOS 判断数组array中是否包含元素a,取出a在array中的下标+数组方法详解
目前找到来4个解决办法,第三个尤为简单方便 NSArray * arr = @["]; //是否包含 "]) { NSInteger index = [arr indexOfObj ...
- js 类数组arguments详解
arguments并不是一个真正的数组,而是一个"类似数组(array-like)"的对象: 就像下面的这段输出,就是典型的类数组对象: [, , callee: ƒ, Symbo ...
随机推荐
- pg序列的增删改查
添加序列. CREATE SEQUENCE IF NOT EXISTS public.data_device_id_seq INCREMENT 1 START 1 MINVALUE 1 MAXVALU ...
- 2021-08-12:约瑟夫环问题。给定一个链表头节点head,和一个正数m,从头开始,每次数到m就杀死当前节点 ,然后被杀节点的下一个节点从1开始重新数, 周而复始直到只剩一个节点,返回最后的节点。
2021-08-12:约瑟夫环问题.给定一个链表头节点head,和一个正数m,从头开始,每次数到m就杀死当前节点 ,然后被杀节点的下一个节点从1开始重新数, 周而复始直到只剩一个节点,返回最后的节点. ...
- IBM小型机 - AIX系统配置IP
AIX系统网口配置IP 前言 新部署的系统都是要通过IP来访问的,但是AIX系统配置IP的方式和Linux的不一样: 为了配置后可以通过远程访问系统,我们要给网口配置上IP. 操作步骤 1.新部署的A ...
- uni-app 运行项目
运行-运行到浏览器-选择浏览器 运行新项目:真机测试需要打开真机的开发者选项usb安装调试功能
- [ABC270D] Stones
[ABC270D] Stones 题意 有两个人玩游戏,有 \(n\) 个石子,和一个长度为 \(k\) 的序列,每次可以取 \(a_i\) 个但前提是剩下来的石子数有 \(a_i\) 个,第一个人先 ...
- 一天吃透Spring面试八股文
内容摘自我的学习网站:topjavaer.cn Spring是一个轻量级的开源开发框架,主要用于管理 Java 应用程序中的组件和对象,并提供各种服务,如事务管理.安全控制.面向切面编程和远程访问等. ...
- The shell
The shell shell是什么? 如今的计算机有着多种多样的交互接口让我们可以进行指令的的输入,从炫酷的图像用户界面(GUI),语音输入甚至是 AR/VR 都已经无处不在. 这些交互接口可以覆盖 ...
- Dapr在Java中的实践 之 服务调用
服务调用 通过服务调用(Service-to-service Invocation),服务可以使用 gRPC 或 HTTP 这样的标准协议来发现并可靠地与其他服务通信. Dapr采用边车(Sideca ...
- 【python基础】复杂数据类型-字典(遍历)
一个字典可能只包含几个键值对,也可能包含数百万个键值对,所以Python支持字典遍历.字典可用于以各种方式存储信息,因此有多种遍历字典的方式:可遍历字典的所有键值对.键或值. 1.遍历所有的键值对 其 ...
- 浅谈 thinkphp composer 扩展包加载原理
浅谈 thinkphp composer 扩展包加载原理 本文将介绍 ThinkPHP 中 Composer 扩展包的加载原理,帮助读者更好地理解和应用该功能. 前言 如题,今天感觉好久没有更新博客了 ...