【笔记】后缀数组

后缀数组 (SA)

0 约定

字符串下标从 \(1\) 开始。

字符串 \(s\) 的长度为 \(n\)

「后缀 \(i\)」:指从 \(i\) 开始的 \(s\) 的后缀,即 \(s[i\dots n]\)​。


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^\prime[i][j]\) 表示从 \(i\) 开始往后的长度为 \(2^j+1\) 的字符串,即 \(S[i\dots i+2^j]\),排序后的位置。

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


2.2 实现

2.2.1 易错

  1. 注意 \(rk^\prime\) 的右端点是很可能超过 \(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 @ 2024-03-07 15:52  CloudWings  阅读(9)  评论(1编辑  收藏  举报