后缀数组小结

前言

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

简介

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

做法

考虑暴力:把每个后缀提出来然后快排,时间复杂度(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))

原文地址:https://www.cnblogs.com/y2823774827y/p/10493129.html