[学习笔记]后缀相关算法

SA

SA实际上求出两个数组\(sa,rk\)。

\(sa_i\)表示将所有后缀排序后排名第\(i\)小的后缀的编号,\(rk_i\)表示后缀\(i\)的排名。

满足其性质\(sa_{rk_i} = rk_{sa_i} = i\)

这里仅给出一个\(O(nlog^2n)\)的做法。

其他做法参见\(oiwiki\)。

点击查看代码
bool cmp(int i,int j){
	if(rk[i]!=rk[j]){
		return rk[i]<rk[j];
	}else{
		int ri,rj;
		ri=i+k<n?rk[i+k]:-1;
		rj=j+k<n?rk[j+k]:-1;
		return ri<rj;
	}
}

void calc(){
	for(int i=0;i<n;i++){
		rk[i]=s[i];
		sa[i]=i;
	}
	for(k=1;k<n;k*=2){
		sort(sa,sa+n,cmp);
		tmp[sa[0]]=0;
		for(int i=0;i<n-1;i++){
			tmp[sa[i+1]]=tmp[sa[i]]+cmp(sa[i],sa[i+1]);
		}
		for(int i=0;i<n;i++){
			rk[i]=tmp[i];
		}
	}
}

很多情况下我们都要求出辅助数组\(height\)数组。

\(height_i = lcp(sa_i,sa_{i - 1})\)

其有引理\(height_{rk_{i - 1}} + 1 \leq height_{rk_{i}}\)

所以根据该引理,维护一个指针即可\(O(n)\)求出其。

点击查看代码

for (i = 1, k = 0; i <= n; ++i) {
  if (rk[i] == 0) continue;
  if (k) --k;
  while (s[i + k] == s[sa[rk[i] - 1] + k]) ++k;
  height[rk[i]] = k;
}

其应用大概有:

两个子串最长公共前缀:

\(lcp(sa_i,sa_j) = \min{heaight_{i + 1...j}}\)

不同子串数目:

\(\frac{n(n+1))}{2} - \sum_{i = 2}^n height_i\)

连续的相同子串([NOI2016] 优秀的拆分):

考虑枚举长度然后设置关键点。
若存在\(AA\),则\(AA\)一定跨越两个关键点,枚举两关键点,其一定有\([1,x][1,y]\)的后缀最长公共子串加\([x,n][y,n]\)的前缀最长公共子串大于枚举长度。
配合图食用。
[学习笔记]后缀相关算法

搭配其他数据结构

上一篇:微信小程序---自定义顶部导航组件


下一篇:简单实现滑块组件且隐藏滚动条——项目笔记篇