后缀数组学习笔记

做题做到一半突然发现自己不会后缀数组(?)


后缀数组主要由两个数组组成,sark

sa[i] 表示开头位置为 i 的后缀的字典序排名。

rk[i] 表示字典序排名为 i 的后缀的开头位置。

可以采用倍增法求解:

考虑对于 \(w=2^k\),求出仅考虑长度为 \(w\) 的前缀时的 sa 以及 rk,那么利用 \(i\)\(i+w\) 的信息就可以推导出 \(k+1\) 时的 sark

我们考虑求出 sa,然后推出 rk

最简单的方法是利用双关键字排序,令 sa[i] 为第一关键字,sa[i+w] 为第二关键字,排序即可得到 \(k+1\) 时的 sa

进阶的方法是考虑 sa[i+w]sa[i] 的影响,我们找到数组 id[i] 表示第二关键字排名为 \(i\) 的后缀的开头位置。

那么只需要按类似的方法扫一遍即可。rk 的递推方法类似,具体可以看代码。

#define rep(i,a,b) for(int i(a);i<(b);++i)
#define dep(i,a,b) for(int i(a-1);i>=(b);--i)

std::pair<std::vector<int>, std::vector<int>> SuffixArray(const std::string& str) {
	int n = str.length(), m = 0xff;
	std::vector<int> rk(n, 0), sa(n, 0), _rk(n, 0), id(n, 0), cnt(std::max(n, m), 0);
	
	rep(i, 0, n) ++cnt[rk[i] = str[i]];
	rep(i, 1, m) cnt[i] += cnt[i-1];
	dep(i, n, 0) sa[--cnt[rk[i]]] = i;
	
	for(int w = 1;; w <<= 1) {
		int j = 0, p = 0;
		rep(i, n-w, n) id[j++] = i;
		rep(i, 0, n) if(sa[i] >= w) id[j++] = sa[i] - w;
		
		std::fill(cnt.begin(), cnt.begin() + m, 0);
		rep(i, 0, n) ++cnt[rk[i]];
		rep(i, 1, m) cnt[i] += cnt[i-1];
		dep(i, n, 0) sa[--cnt[rk[id[i]]]] = id[i];
		
		_rk.swap(rk);
		auto at = [&](int x) ->int {return x >= n ? -1 : _rk[x];};
		rk[sa[0]] = p = 0;
		rep(i, 1, n)
			rk[sa[i]] = (at(sa[i]) != at(sa[i-1]) || at(sa[i] + w) != at(sa[i-1] + w)
				? ++p : p);
		if((m = ++p) == n) break;
	}
	
	return {sa, rk};
}

就酱。


height 数组,下文简称为 h,表示 LCP(sa[i],sa[i-1])

用来求任意 LCP 的。

显然两个后缀的 LCP 就等于他们的 rk 之间的 h 的最小值。

有一个性质是 \(\text{h[rk[i]]}\ge\text{h[rk[i-1]]-1}\)

于是暴力枚举即可。

posted @ 2025-07-16 01:57  CuteNess  阅读(8)  评论(0)    收藏  举报