后缀数组SA超详细讲解

(标题是用来钓鱼的<( ̄3 ̄)> )

  重新回顾了一下SA的板子,感觉理解得更深了。细节看码。

#include <bits/stdc++.h>

#define rep(i, a, b) for (int i = a, i##end = b; i <= i##end; ++i)
#define per(i, a, b) for (int i = a, i##end = b; i >= i##end; --i)
#define rep0(i, a) for (int i = 0, i##end = a; i < i##end; ++i)
#define per0(i, a) for (int i = a-1; ~i; --i)
#define chkmin(a, b) a = std::min(a, b)
#define chkmax(a, b) a = std::max(a, b)

typedef long long ll;

const int maxn = 1111111;

char s[maxn];
int sa[maxn];

void build_sa(char *s, int *sa) {
	static int t[maxn << 1], t2[maxn << 1], c[maxn], *x, *y, n, m;
    // t,t2是两个数组(要开两倍,否则下面比较会溢出),x和y将会指向它们;c是基数排序的桶;n表示长度,m表示字符集大小
	x = t, y = t2, n = strlen(s), m = 'z'+1;
    // 往下,x存第一关键字的值
	rep0(i, m) c[i] = 0; // 清空桶
	rep0(i, n) c[x[i] = s[i]]++; // 将字符串转化到x数组中(初始情况下第一关键字即为单个字符)并放入桶中
	rep(i, 1, m-1) c[i] += c[i-1]; // 前缀累加可以方便查询排名
	per0(i, n) sa[--c[x[i]]] = i; // 把后缀按照排名(此时长度为1)放入后缀数组中,注意要倒过来,保证串短的在前
	for (int k = 1; k < n; k <<= 1) { // 倍增(长度为2k)
		int p = 0; // 一变量多用。用来当y数组的指针;之后表示新的字符集大小
		rep(i, n-k, n-1) y[p++] = i; // 第二关键字为Φ的后缀位置先排
		rep0(i, n) if (sa[i] >= k) y[p++] = sa[i]-k;
        // 用sa来排第二关键字(不存在第二关键字为<k的位置)
		rep0(i, m) c[i] = 0; // 跟前面类似
		rep0(i, n) c[x[y[i]]]++;
		rep(i, 1, m-1) c[i] += c[i-1];
		per0(i, n) sa[--c[x[y[i]]]] = y[i];
        // 注意一定要倒过来取排名,保证第二关键字排序结果正序
		std::swap(x, y); // 快速交换数组(指针)
		p = 1, x[sa[0]] = 0; // 安排排名第一的权值
		rep(i, 1, n-1)
			x[sa[i]] = y[sa[i]] == y[sa[i-1]] && y[sa[i]+k] == y[sa[i-1]+k] ? p-1 : p++;
        // 比对第一关键字和第二关键字
		if ((m = p) == n) return; // 如果字符集=n,显然没有必要再排了
	}
}

int main() {
	scanf("%s", s);
	build_sa(s, sa);
	rep0(i, strlen(s)) printf("%d ", sa[i]+1);
	return 0;
}
posted @ 2020-05-14 20:59  AC-Evil  阅读(357)  评论(4编辑  收藏  举报