后缀数组 SA

先这样,之后补。

定义 \(sa_i\) 为字典序第 \(i\) 小的后缀的开头位置。

定义 \(rk_i\)\(i\) 开头的后缀在所有后缀中的相对大小。

\(sa_{rk_i}=i,rk_{sa_i}=i\)

后缀排序

快速求 \(sa_i,rk_i\)。这里给 \(O(n\log n)\) 的做法。

考虑倍增维护,记当前倍增长度为 \(len\)。那么对于两个长度为 \(len\) 的字符串 \(s_1,s_2\),有:

  1. \(s_{1,1\dots \frac{len}{2}} < s_{2,1\dots \frac{len}{2}}\)。则 \(s_1 < s_2\)
  2. \(s_{1,1\dots \frac{len}{2}} > s_{2,1\dots \frac{len}{2}}\)。则 \(s_1 > s_2\)
  3. \(s_{1,1\dots \frac{len}{2}} = s_{2,1\dots \frac{len}{2}}\)
    1. \(s_{1,\frac{len}{2}+1\dots len} < s_{2,\frac{len}{2}+1\dots len}\)。则 \(s_1 < s_2\)
    2. \(s_{1,\frac{len}{2}+1\dots len} > s_{2,\frac{len}{2}+1\dots len}\)。则 \(s_1 > s_2\)
    3. \(s_{1,\frac{len}{2}+1\dots len} = s_{2,\frac{len}{2}+1\dots len}\)。则 \(s_1 = s_2\)

因为我们是倍增,所以已经求出 \(len' = \frac{len}{2}\) 时任意两个串的相对大小了,所以只需要将两个 \(\frac{len}{2}\) 的连续字符串的 \(rk\) 看成两个关键字,然后排序。

直接基数排序可以做到 \(O(n\log n)\)。优化常数可以考虑按照第二关键字排序的时候实际上 \(\frac{len}{2}+1 > n\) 的位置都会排在前面,且剩余的可以直接枚举 \(sa_i\) 插入。形如:

U(i, n - w + 1, n) id[++ idx] = i;
U(i, 1, n) if(sa[i] > w) id[++ idx] = sa[i] - w;

这里 \(id_x\) 为第二关键字排序后第 \(i\) 小的长度为 \(2w\) 的开头。那么后缀排序可以写成:

int m = 128, idx = 0;
U(i, 1, n) ++ cnt[rk[i] = s[i]];
U(i, 1, m) cnt[i] += cnt[i - 1];
D(i, n, 1) sa[cnt[rk[i]] --] = i;
for(re int w = 1; ; w *= 2){
	idx = 0;
	U(i, n - w + 1, n) id[++ idx] = i;
	U(i, 1, n) if(sa[i] > w) id[++ idx] = sa[i] - w;
	U(i, 0, m) cnt[i] = 0;
	U(i, 1, n) ++ cnt[rk[i]];
	U(i, 1, m) cnt[i] += cnt[i - 1];
	D(i, n, 1) sa[cnt[rk[id[i]]] --] = id[i];
	U(i, 1, n) rk_[i] = rk[i];
	m = 0;
	U(i, 1, n){
		if(rk_[sa[i]] == rk_[sa[i - 1]] && rk_[sa[i] + w] == rk_[sa[i - 1] + w]) rk[sa[i]] = m;
		else rk[sa[i]] = ++ m;
	}
	if(n == m) break;
}

\(\operatorname{height}\) 数组

定义 \(height_i = \operatorname{LCP}(sa_{i-1},sa_i)\)。也就是相对大小相邻的两个后缀的最长公共前缀。

有引理:\(height_{rk_i}\ge height_{rk_{i-1}}-1\)。证明是简单的,那么可以 \(O(n)\) 求出 \(height\) 数组了。

U(i, 1, n){
	ht[rk[i]] = max(0, ht[rk[i - 1]] - 1);
	while(s[i + ht[rk[i]]] == s[sa[rk[i] - 1] + ht[rk[i]]]) ++ ht[rk[i]];
}
posted @ 2026-03-05 19:27  harmis_yz  阅读(1)  评论(0)    收藏  举报