后缀数组总结

问题引入

现在要求一个字符串中至少出现(k)次(可以重叠)的子串的最大长度。

问题解决

暴力

首先我们可以暴力找一个子串,暴力判断这个子串出现次数,暴力计算,这样子复杂度是(Theta(n^3))的。

SA

考虑一种全新的做法,后缀数组。

定义

(rnk_i)表示第(i)个后缀按照字典序排名为(rnk_i)
(sa_i)表示按照字典序排名为(i)的后缀是(sa_i)
(height_i)表示(sa_i)(sa_i-1)(LCP)(最长公共前缀)

求法

考虑倍增求解,当前如果解决了长度为(len)的然后准备扩展到(len*2),那么显然需要把后缀补上然后再算前面算好了的。

这个具体的实现就是两个桶然后桶排。(可以参考代码)

代码实现

后缀数组的代码还是挺好记的(容易理解+1)

void get_sa(){
	int m=100;
	for(int i=1;i<=m;i++)t[i]=0;
	for(int i=1;i<=n;i++)t[x[i]=a[i]]++;
	for(int i=1;i<=m;i++)t[i]+=t[i-1];
	for(int i=n;i;i--)sa[t[x[i]]--]=i;
	int p=0;
	for(int lim=1;p<=n && lim<=n;lim<<=1){
		p=0;
		for(int i=n-lim+1;i<=n;i++)y[++p]=i;
		for(int i=1;i<=n;i++)if(sa[i]>lim)y[++p]=sa[i]-lim;
		for(int i=1;i<=m;i++)t[i]=0;
		for(int i=1;i<=n;i++)t[x[y[i]]]++;
		for(int i=1;i<=m;i++)t[i]+=t[i-1];
		for(int i=n;i;i--)sa[t[x[y[i]]]--]=y[i];
		swap(x,y);x[sa[1]]=1;p=2;
		for(int i=2;i<=n;i++)x[sa[i]]=cmp(sa[i],sa[i-1],lim)?p-1:p++;
		m=p;
	}
	for(int i=1;i<=n;i++)rnk[sa[i]]=i;
	for(int i=1,j=0;i<=n;i++){
		j=max(0,j-1);int lst=sa[rnk[i]-1];
		while(a[i+j]==a[lst+j])j++;
		height[rnk[i]]=j;
	}
}

例题

Problem A

Hihocoder1403

Solution A

就是后缀数组的模板,如何判断最长长度?二分答案即可。

Problem B

Hihocoder1407

Solution B

考虑两个后缀的(lcp)的长度,如果(ge mid)且这两个后缀之间的长度也(ge mid)那么显然就是一种可行的对吧。
然后直接二分答案然后判断即可,相当于是按照(height)分组。

Problem C

Hihocoder1415

Solution C

显然可以把两个串拼起来,然后只需要判断两个相邻的后缀是否属于不同的串然后取(max_{i=1}^nheight_i)即可。

Problem D

Hihocoder1419

Solution D

可以说是这四道题目里面最难的一道题目了(毕竟是4)

考虑枚举长度和起始位置,这样子是(Theta(n^2))的。

如何优化?显然可以只枚举(i-1)(l)的倍数的位置,因为如果长度是(l)但不在(i)起始,显然会有一段多出来的接在后面,直接再求一次(lcp)(max)即可。

原文地址:https://www.cnblogs.com/fexuile/p/11625851.html