后缀定义

“后缀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++详解的更多相关文章

  1. js数组方法详解

    Array对象的方法-25个 /*js数组方法详解 */ /* * 1 concat() 用于连接多个数组或者值-------------- * 2 copyWithin() 方法用于从数组的指定位置 ...

  2. JavaScript数组方法详解

    JavaScript数组方法详解 JavaScript中数组的方法种类众多,在ES3-ES7不同版本时期都有新方法:并且数组的方法还有原型方法和从object继承的方法,这里我们只介绍数组在每个版本中 ...

  3. 最新java数组的详解

    java中HashMap详解 http://alex09.iteye.com/blog/539545 总结: 1.就像引用类型的数组一样,当我们把 Java 对象放入数组之时,并不是真正的把 Java ...

  4. JS数组映射详解

    现在这里占个坑位,免的忘了,需要整理一下最近的内容: 1.数组映射的使用 2.微信分享功能详解 3.jq自己封装 4.HTML的富文本应用

  5. [转载]Splay Tree数组实现+详解

    变量声明:f[i]表示i的父结点,ch[i][0]表示i的左儿子,ch[i][1]表示i的右儿子,key[i]表示i的关键字(即结点i代表的那个数字),cnt[i]表示i结点的关键字出现的次数(相当于 ...

  6. JavaScript Array数组方法详解

    Array类型是ECMAScript中最常用的引用类型.ECMAScript中的数据与其它大多数语言中的数组有着相当大的区别.虽然ECMAScript中的数据与其它语言中的数组一样都是数据的有序列表, ...

  7. [转]Java数组初始化详解

    一维数组1)   int[] a;   //声明,没有初始化 2)   int[] a=new int[5];   //初始化为默认值,int型为0 3)   int[] a={1,2,3,4,5}; ...

  8. javascript数组常用方法详解

    1,splice().   array.splice(index,many,list1,list2....)  参数1.index位置 负数为从结尾处算,倒数第一为-1:参数2,many要删除的项目, ...

  9. iOS 判断数组array中是否包含元素a,取出a在array中的下标+数组方法详解

    目前找到来4个解决办法,第三个尤为简单方便 NSArray * arr = @["]; //是否包含 "]) { NSInteger index = [arr indexOfObj ...

  10. js 类数组arguments详解

    arguments并不是一个真正的数组,而是一个"类似数组(array-like)"的对象: 就像下面的这段输出,就是典型的类数组对象: [, , callee: ƒ, Symbo ...

随机推荐

  1. vue【解决方案】页面/路由跳转后,滚动条消失,页面无法滚动

    原因解析: vue项目中,页面/路由跳转后,body 的内联样式变成 overflow:hidden 解决方案: 使用路由守卫,在页面/路由跳转后,将body 的overflow设置为auto src ...

  2. 2022-07-09:总长度为n的数组中,所有长度为k的子序列里,有多少子序列的和为偶数?

    2022-07-09:总长度为n的数组中,所有长度为k的子序列里,有多少子序列的和为偶数? 答案2022-07-09: 方法一:递归,要i还是不要i. 方法二:动态规划.需要两张dp表. 代码用rus ...

  3. 2022-01-18:将数组分成两个数组并最小化数组和的差。 给你一个长度为 2 * n 的整数数组。你需要将 nums 分成 两个 长度为 n 的数组,分别求出两个数组的和,并 最小化 两个数组和之

    2022-01-18:将数组分成两个数组并最小化数组和的差. 给你一个长度为 2 * n 的整数数组.你需要将 nums 分成 两个 长度为 n 的数组,分别求出两个数组的和,并 最小化 两个数组和之 ...

  4. vue全家桶进阶之路44:Vue3 Element Plus el_row和el_col组件

    在 Vue 3 中,Element Plus 也提供了 ElRow 和 ElCol 组件,用于实现栅格布局. ElRow 组件的常用属性: gutter:栅格间距,默认为 0. type:布局模式,可 ...

  5. 《最新出炉》系列初窥篇-Python+Playwright自动化测试-1-环境准备与搭建

    1.简介 有很多人私信留言宏哥问能不能介绍一下Playwright这款自动化神器的相关知识,现在网上的资料太少了.其实在各大博客和公众号也看到过其相关的介绍和讲解.要不就是不全面.不系统,要不就是系统 ...

  6. python 环境下使用PIP 报错的解决方法

    最近做一个小程序项目,使用djangorestframework,安装restframework 出现错误,安装环境Python2.7:出现错误如下:  "UnicodeEncodeErro ...

  7. find提权

    更新中.............. find 常用参数 语法:find [path-] [expression] path为查找路径,.为当前路径,/为根目录 expression即为参数 -name ...

  8. Godot 4.0 遮罩一个2D物体,使其部分显示

    本文针对Godot 4.0. 我也查到了Godot 3.5如何实现遮罩,见这个链接 https://ask.godotengine.org/3031/how-do-i-mask-a-sprite 由于 ...

  9. @Retention元注解的使用

    @Retention注解标记其他的注解用于指明标记的注解保留策略:先看Java SE 8中@Target是如何声明的: package java.lang.annotation; public enu ...

  10. GPU技术在大规模计算和并行计算中的应用和挑战

    目录 1. 引言 2. 技术原理及概念 3. 实现步骤与流程 4. 应用示例与代码实现讲解 5. 优化与改进 GPU 技术在大规模计算和并行计算中的应用和挑战 随着计算机硬件的不断发展和计算能力的提高 ...