后缀数组

0x00 前言

  • 似乎有必要重新深刻理解一下后缀数组
  • 于是我就重学了一遍后缀数组

0x01 简介

  • 后缀数组(sa)就是把一个字符串S的所有后缀按字典序排序以后的数组
  • sa[i]:排第i名的是哪个后缀
  • rk[i]:第i个后缀排第几名
  • sa[rk[i]] = i;
  • height[i]:第sa[i]个后缀和第sa[i-1]个后缀的LCP

0x02 求法

  • 先说求法咯
  • 可以二分哈希来两个log求
  • 一般都是倍增
  • 先上代码
void get_sa() {
	int i, j, k, sz = MAXINT;
	for (i = 0; i < sz; i++) cnt[i] = 0;
	for (i = 0; i < len; i++) cnt[trk[i] = s[i]]++;
	for (i = 1; i < sz; i++) cnt[i] += cnt[i - 1];
	for (i = len - 1; i >= 0; i--) sa[--cnt[s[i]]] = i;
	for (j = 1, k = 1; k < len; j <<= 1, sz = k) {
		for (k = 0, i = len - j; i < len; i++) tsa[k++] = i;
		for (i = 0; i < len; i++) if (sa[i] >= j) tsa[k++] = sa[i] - j;
		for (i = 0; i < len; i++) tstr[i] = trk[tsa[i]];
		for (i = 0; i < sz; i++) cnt[i] = 0;
		for (i = 0; i < len; i++) cnt[tstr[i]]++;
		for (i = 1; i < sz; i++) cnt[i] += cnt[i - 1];
		for (i = len - 1; i >= 0; i--) sa[--cnt[tstr[i]]] = tsa[i];
		for (swap(tsa, trk), k = 1, trk[sa[0]] = 0, i = 1; i < len; i++) {
			trk[sa[i]] = cmp(tsa, sa[i], sa[i - 1], j) ? k - 1 : k++;
		}
	}
}
void get_height() {
	int i, j, k;
	for (i = 0; i < len; i++) rk[sa[i]] = i;
	for (i = 0, k = 0; i < len - 1; height[rk[i++]] = k) {
		for (k ? k-- : 0, j = sa[rk[i] - 1]; s[i + k] == s[j + k]; k++);
	}
}
  • 倍增是个很妙的东西,一点点来解释
for (i = 0; i < sz; i++) cnt[i] = 0;
for (i = 0; i < len; i++) cnt[trk[i] = s[i]]++;
for (i = 1; i < sz; i++) cnt[i] += cnt[i - 1];
for (i = len - 1; i >= 0; i--) sa[--cnt[s[i]]] = i;
  • trk下储存了按第一关键字的排名,sa储存每一遍排序完了以后的后缀数组,这就是一个简单的基数排序的过程,顺便初始化了第一关键字(现在就是一个字符)的大小关系
for (j = 1, k = 1; k < len; j <<= 1, sz = k) 
  • j是关键字长度,sz是字符集大小
for (k = 0, i = len - j; i < len; i++) tsa[k++] = i;
  • tsa中存放了按照第二关键字排序的后缀数组,后面的一些后缀根本没有第二关键字,所以它们的排名是最小的
for (i = 0; i < len; i++) if (sa[i] >= j) tsa[k++] = sa[i] - j;
  • 然后由于sa已经是按照第一关键字排完序的了,所以我们可以直接利用sa求出剩下的tsa,发现某个排名的后缀(sa[i])的启示位置大于j,他才是sa[i]-j这个位置的后缀的第二关键字(不然sa[i]-j就小于0了)
for (i = 0; i < len; i++) tstr[i] = trk[tsa[i]];
  • 然后呢?然后我们得到了按第二关键字排完序的后缀数组,我们只需要按第一关键字再排一遍序即可,这个是把按照第二关键字排完序的后缀数组对应的第一关键字给拎出来
for (i = 0; i < sz; i++) cnt[i] = 0;
for (i = 0; i < len; i++) cnt[tstr[i]]++;
for (i = 1; i < sz; i++) cnt[i] += cnt[i - 1];
for (i = len - 1; i >= 0; i--) sa[--cnt[tstr[i]]] = tsa[i];
  • 基数排序,没啥好说的,注意这里的tstr在性质上更像rk数组,而不是sa
for (swap(tsa, trk), k = 1, trk[sa[0]] = 0, i = 1; i < len; i++) {
	trk[sa[i]] = cmp(tsa, sa[i], sa[i - 1], j) ? k - 1 : k++;
}
  • 然后我们希望根据trk和sa求出新的trk。怎么做呢?你应该记得rk[sa[i]]=i,但现在我们还没拍完序,后缀之间可能相等,我们只要比较sa[i]和sa[i-1]是不是一样就行了,具体比较的时候只要比较trk[sa[i]]和trk[sa[i-1]],trk[sa[i]+j]和trk[sa[i-1]+j]是否相等即可(是否第一第二关键字都对应相等)现在tsa已经没用了,先把trk暂时存放在tsa里面好了
int i, j, k;
for (i = 0; i < len; i++) rk[sa[i]] = i;
for (i = 0, k = 0; i < len - 1; height[rk[i++]] = k) {
	for (k ? k-- : 0, j = sa[rk[i] - 1]; s[i + k] == s[j + k]; k++);
}
  • 一个性质,height[rk[i]]>=height[rk[i-1]]-1,根据这个就可以线性求height了,我不会证qwq

0x03 用处

  • height的用处非常多
  • 因为height数组中两个后缀的位置之间的最小值就是他们的LCP,height又可以求ST表
  • 于是可以干各种乱七八糟的事,比如匹配啊,计数啊
原文地址:https://www.cnblogs.com/LoveYayoi/p/6907812.html