【笔记】后缀数组

后缀数组 (SA)

0 约定

字符串下标从 1 开始。

字符串 s 的长度为 n

「后缀 i」:指从 i 开始的 s 的后缀,即 s[in]​。


1 定义

这个字符串的所有非空后缀按字典序(用 ASCII 数值比较)从小到大排序,然后按顺序输出后缀的第一个字符在原串中的位置。

后缀数组,sa[i] 表示将一个字符串的所有非空后缀按字典序排序后,从小到大的第 i 大的后缀的起点。就是问她 "第 i 个是哪个后缀?"。

排名数组,rk[i] 表示后缀 i 的排名(Warning 这里只是为后文叙述方便引入的辅助数组,也没有严格的名称)。就是问她 "第 i 个后缀排哪?"。

显然,这两个数组满足 sa[rk[i]]=rk[sa[i]]=i​。


2 求解

2.1 思想

【模板】后缀排序

先谈更 Native 的倍增。她较线性的 DC3 优势在于实现容易,常数小,空间复杂度小,而时间复杂度也只多一个 log


字典序总是具有可以贪心的性质,从贪心入手,类比 ST 表,我们另记 rk[i][j] 表示从 i 开始往后的长度为 2j+1 的字符串,即 S[ii+2j],排序后的位置。

然后,我们枚举 j,考虑转移,对 rk[i][j] 她其实就等价于 (rk[i][j1],rk[i+2j1][j]) 这个二元组,我们对这 n 个二元组排序,即可得到新的 rk


2.2 实现

2.2.1 易错

  1. 注意 rk 的右端点是很可能超过 n 的,但是因为 s 后面都是 \0,比所有的小写字母都小,所以没有影响。如果多测就要注意了。

2.2.2 过程

  1. 首先对

2.3 Code

/*
 * Time Spent: 
 * Solution: 
	 Tag: 
 * Summary: 
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
char s[N];
int n, sa[N], rk[N], w[N], sw[N], x[N], y[N];
int main () {
	#ifdef LOCAL
	freopen("_1.in", "r", stdin);
	freopen("_1.out", "w", stdout);
	#else
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	#endif
	cin >> s+1, n = strlen(s+1);
	int m = max(n, 1000);
	for (int i = 1; i <= n; i++) w[s[i]]++;
	for (int i = 1; i <= m; i++) sw[i] = sw[i-1] + w[i];
	for (int i = 1; i <= n; i++) x[sw[s[i]]--] = i;
	for (int i = 1; i <= n; i++)
		rk[x[i]] = rk[x[i-1]] + !(s[x[i]] == s[x[i-1]]);
	for (int j = 1; j <= n; j <<= 1) {
		memset(w, 0, sizeof w);
		for (int i = 1; i <= n; i++) w[rk[i+j]]++;
		sw[0] = w[0];
		for (int i = 1; i <= m; i++) sw[i] = sw[i-1] + w[i];
		for (int i = 1; i <= n; i++) x[sw[rk[i+j]]--] = i;
		memset(w, 0, sizeof w);
		for (int i = 1; i <= n; i++) w[rk[i]]++;
		for (int i = 1; i <= m; i++) sw[i] = sw[i-1] + w[i];
		for (int i = n; i; i--)
			y[sw[rk[x[i]]]--] = x[i];
		static int r[N];
		memset(r, 0, sizeof r);
		for (int i = 1; i <= n; i++)
			r[y[i]] = r[y[i-1]] + !(rk[y[i]] == rk[y[i-1]] && rk[y[i]+j] == rk[y[i-1]+j]);
		memcpy(rk, r, sizeof r);
	}
	for (int i = 1; i <= n; i++) sa[rk[i]] = i;
	for (int i = 1; i <= n; i++) cout << sa[i] << ' ';
	return 0;
}
/*
g++ _1.cpp -o _1 -O2 -std=c++14 -DLOCAL; ./_1.exe
*/

对于一个子串,出现它的后缀的排名是一段连续的区间。可以二分左右边界求得。

posted @   CloudWings  阅读(10)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· [翻译] 为什么 Tracebit 用 C# 开发
· 腾讯ima接入deepseek-r1,借用别人脑子用用成真了~
· Deepseek官网太卡,教你白嫖阿里云的Deepseek-R1满血版
· DeepSeek崛起:程序员“饭碗”被抢,还是职业进化新起点?
· 深度对比:PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
点击右上角即可分享
微信分享提示