后缀数组(SA)

后缀数组

· 前言

· 定义

后缀数组分为两个数组:
\(sa_i\) 表示第 \(i\) 大的后缀的第一个字符在原串中的位置
\(rk_i\) 表示第一个字符在原串中的位置为 \(i\) 的后缀的排名。(两个相同的后缀排名相同)

易知:
\(rk_{sa_i}\) 表示第 \(i\) 名。
\(sa_{rk_i}\) 表示 \(i\) 这个位置。

· 算法流程

\(O(nlog^2n)\)做法

我们考虑倍增,已知长度为 \(x\)\(sa_i\)\(rk_i\) 推长度为 \(2x\)\(sa'_i\)\(rk'_i\)
\(sa'_i\)

  • 以前 \(x\) 个字符为第一关键字。
    \(x\) 个字符为第二关键字。

排序的结果。

理解即可
rpt(i,1,n)sa[i]=i;
//此时 sa 的值不是正确的意义但仍可以保证正确 
rpt(i,1,n)rk[i]=s[i];
//因为 rk 的值是正确的 
for(int w=1;w<n;w<<=1){
	//w 为上一组的字符串长度 
	sort(sa+1,sa+1+n,[&](int x,int y){
		return rk[x]==rk[y]?rk[x+w]<rk[y+w]:rk[x]<rk[y];
	});
	//排名离散化 
	int now=0;
	rpt(i,1,n){
		if(rk[sa[i]]!=rk[sa[i-1]]||rk[sa[i]+w]!=rk[sa[i-1]+w])now++;
		tmprk[sa[i]]=now;
		//不能直接修改 rk 
	}
	rpt(i,1,n)rk[i]=tmprk[i];
}
\(O(nlogn)\)做法

我们考虑到 \(rk_i\) 的值域并不会超过 \(n\)
所以我们把排序换成基数排序即可。

优化前
int v=0;
rpt(i,1,n)v=max(v,(int)s[i]);
// v 表示基数排序值域 
rpt(i,1,n)rk[i]=s[i];

rpt(i,0,v)t[i]=0;
rpt(i,1,n)t[rk[i]]++;
rpt(i,1,v)t[i]+=t[i-1];
rpt(i,1,n)sa[t[rk[i]]--]=i;
//此时 sa 的值需要表示正确的意义
for(int w=1;w<n;w<<=1){
	//w 为上一组的字符串长度 
	rpt(i,0,v)t[i]=0;
	rpt(i,1,n)t[rk[i+w]]++;
    //以 rk[i+w] 为关键字
	rpt(i,1,v)t[i]+=t[i-1];
	rpt(i,1,n)sa[t[rk[i+w]]--]=i;
	//第二关键字排序
	rpt(i,0,v)t[i]=0;
	rpt(i,1,n)id[i]=sa[i];
	rpt(i,1,n)t[rk[id[i]]]++;
    //以 rk[i] 为关键字
	rpt(i,1,v)t[i]+=t[i-1];
	tpr(i,n,1)sa[t[rk[id[i]]]--]=id[i];
    //倒序枚举排名以保证相同大小按原顺序排序
    //sa 会被改变所以用 id 存一下
	//第一关键字排序
	//排名离散化 
	int now=0;
	rpt(i,1,n){
		if(rk[sa[i]]!=rk[sa[i-1]]||rk[sa[i]+w]!=rk[sa[i-1]+w])now++;
		tmprk[sa[i]]=now;
		//不能直接修改 rk 
	}
	v=now;
	rpt(i,1,n)rk[i]=tmprk[i];
}

其实我们会发现上面代码有很多优化空间。

  • \(1.\) 在上一层时 \(rk_i\) 已经有序,所以不需要按第二关键字排序,只需考虑第二关键字为空的情况。
    \(2.\)\(rk_i\) 已经互不相同时,说明已经比出大小,直接结束即可。
优化后
for(int w=1;w<n;w<<=1){
	//w 为上一组的字符串长度 
	int top=0;
	rpt(i,n-w+1,n)id[++top]=i;
	rpt(i,1,n)if(sa[i]-w>0)id[++top]=sa[i]-w;
	rpt(i,1,n)sa[i]=id[i];
	//第二关键字其实只需要考虑空串
	//其他非空集已经有序 
	rpt(i,0,v)t[i]=0;
	rpt(i,1,n)id[i]=sa[i];
	rpt(i,1,n)t[rk[id[i]]]++;
	rpt(i,1,v)t[i]+=t[i-1];
	tpr(i,n,1)sa[t[rk[id[i]]]--]=id[i];
	//第一关键字排序
	//排名离散化 
	int now=0;
	rpt(i,1,n){
		if(rk[sa[i]]!=rk[sa[i-1]]||rk[sa[i]+w]!=rk[sa[i-1]+w])now++;
		tmprk[sa[i]]=now;
		//不能直接修改 rk 
	}
	v=now;
	rpt(i,1,n)rk[i]=tmprk[i];
	if(v==n)break;
}

模板

\(height\) 数组

\(height\) 数组(以下称 \(he_i\) ) 是后缀数组解决任意两个后缀的最长前缀的手段。
定义 \(he_i\)\(sa_i\)\(sa_{i-1}\) 最长公共前缀长度。

\(he_i\) 的求法

重要性质:
\(he_{rk_i} \ge he_{rk_{i-1}}-1\)
证明:

\(he_{rk_{i-1}} = (sa_{rk_{i-1}},sa_{rk_{i-1}-1})\)
\(he_{rk_i} = (sa_{rk_i},sa_{rk_i-1})\)
在排好序的后缀中:
\(sa_{rk_{i-1}}=aAD\) , \(sa_{rk_{i-1}-1}=aAB\)
则有 \(sa_{rk_i}=AD\)
因为后缀 \(AD\) 必然存在。
所以 \(sa_{rk_i-1}=AC\) 且满足 \(D > C \geq B\)
即至多会减少 \(a\) 一个字符。

posted @ 2024-12-13 07:36  C_Wish  阅读(38)  评论(0)    收藏  举报