后缀数组总结

问题引入

现在要求一个字符串中至少出现\(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\)即可。

posted @ 2019-10-05 21:33  fexuile  阅读(172)  评论(0编辑  收藏  举报