后缀数组小结

前言

五分钟应该学完的算法由于看错无数遍变量意义导致\(......\)

简介

利用后缀性质倍增合并实现"快速"排序

做法

考虑暴力:把每个后缀提出来然后快排,时间复杂度\(O(n^2logn)\)
从少到多考虑优化:
\(h~~~~a~~~~v~~~~a~~~~n~~~~a~~~~a~~~~a\)
\(2~~~~1~~~~4~~~~1~~~~3~~~~1~~~~1~~~~1\)
\(21,14,41,13,31,11,11,10\)
\(6~~~~5~~~~8~~~~4~~~~7~~~~2~~~~2~~~~1\)
如果你对\(st\)表还熟悉的话能马上反应过来我们在干什么

其实就是先得出以\(s_i\)开头的长度为\(1\)的排名,再用\(s_{i-1}\)\(s_i\)的排名合并,得到所有长度为\(2\)的排名

通过\(logn\)次倍增合并,最终得到所有后缀的排名

My complete code

还是来稍微解释一下变量意义吧:\(y\)第二关键字的排列,\(x\)第一关键字的排列的映射,\(sa\)当前伪后缀的排列

在排序\(y\)的时候,\([n-len+1,n]\)这些是没有第二关键字的(等同于第二关键字最优)

\((sa[i]>len)\)\(sa[i]\)里存的是位置,当位置\(>len\)时,是可以影响前半的字符串

#include<bits/stdc++.h>
using namespace std;
typedef int LL;
const LL maxn=1e6+9;
LL n,m;
LL c[maxn],x[maxn],y[maxn],sa[maxn];
char s[maxn];
inline void Get_sa(){
	for(LL i=1;i<=n;++i) ++c[x[i]=s[i]];
	for(LL i=2;i<=m;++i) c[i]+=c[i-1];
	for(LL i=n;i>=1;--i) sa[c[x[i]]--]=i;
	for(LL len=1;len<=n;len<<=1){
		LL num(0);
		for(LL i=n-len+1;i<=n;++i) y[++num]=i;
		for(LL i=1;i<=n;++i) if(sa[i]>len) y[++num]=sa[i]-len;
		for(LL i=1;i<=m;++i) c[i]=0;
		for(LL i=1;i<=n;++i) ++c[x[i]];
		for(LL i=2;i<=m;++i) c[i]+=c[i-1];
		for(LL i=n;i>=1;--i) sa[c[x[y[i]]]--]=y[i],y[i]=0;
		swap(x,y);
		x[sa[1]]=num=1;
		for(LL i=2;i<=n;++i)
		    x[sa[i]]=(y[sa[i]]==y[sa[i-1]] && y[sa[i]+len]==y[sa[i-1]+len])?num:++num;
		if(num==n) break;
		m=num;
	}
}
int main(){
	scanf(" %s",s+1);
	n=strlen(s+1); m=122;
	Get_sa();
	for(LL i=1;i<=n;++i) printf("%d ",sa[i]);
	return 0;
}

常用

\(LCP(i,j)\)\(sa[i]\)\(sa[j]\)的最长公共前缀
易证\(LCP(i,j)=min\{LCP(i,k),LCP(k,j)\}\)
那么我们仅需得到\(height[i]=LCP(i,i-1)\)就能通过倍增求得所有的\(LCP\)

\(h[i]=height[rk[i]]\),则\(h[i]≥h[i-1]-1\)
证明:
\(~~~~~\)设第\(i-1\)个字符在\(sa\)里的前驱为\(k\)
\(~~~~~1.\)\(k\)\(i-1\)的首字符不同,则\(h[i-1]=0\),显然\(h[i]≥h[i-1]-1\)
\(~~~~~2.\)\(k\)\(i-1\)的首字符相同,
\(~~~~~\)\(val=\)\(k+1\)的后缀与\(i\)的后缀的最长公共前缀)=\(height[rk[i-1]]-1\)
\(~~~~~\)显然相邻最优所以\(LCP(i,i-1)≥val\),所以\(h[i]≥h[i-1]-1\)

至此我们可以实现\(O(n)\)得到\(height[i]\),通过\(O(nlogn)\)倍增预处理实现在线\(O(logn)\)查询\(LCP(i,j)\)

posted @ 2019-03-07 22:41  y2823774827y  阅读(326)  评论(0编辑  收藏  举报