后缀数组学习笔记

妈呀被后缀数组虐的死去活来。。。一开始去看后缀树。。好像很麻烦的样子,后来再看后缀数组,理论好像很好懂的样子,不会实现呀。。

这充分证明了我有多傻逼,花了整整一天才看懂如何实现,还是因为我基数排序没学好?。。。

(update:然后又花了一天看懂求height。。。)


好好看这张图。。。很重要。。

无脑直接排序的时间复杂度是O(n^2logn)因为字串比较是O(n)

这里介绍用倍增求后缀数组的方法。。

第i次排序将每个字符向后的2^i组成的字符串排序(看上面的图意会一下),无脑排序O(nlogn),但是可以利用后缀间的性质O(n)搞基数排序。

第一次对单个字符排序的时候单独拉出来处理,直接桶排就行了。。从第二次开始,我们要以上一次排序的rank作为第一关键字,再把补上的后一半的字符串上一次的rank作为第二关键字进行排序(看图脑补),这时候开始真正的基数排序。。先维护一个第二关键字递增的序列,后面的没有第二关键字的就视为0,这个可以利用上一次的sa数组弄到(看代码脑补)。。之后把第一关键字一一扔进桶里,然后最关键的地方,按第二关键字递减的顺序把桶里面的东西取出,因为第一关键字一样第二关键字大的更大嘛。。这时候新的sa就搞定了。。然后要维护新的rank,按照离散化的思路顺序处理sa数组即可(看代码脑补)。。

为了发挥后缀数组的威力,我们还要求出height数组,height[i]表示排名第i的后缀和排名的i-1的后缀的最长公共前缀,height[1]=0,然后有一个性质,height[rank[i]]>=height[rank[i]-1]-1,这个看论文证明。这样就可以从1-n顺序的求出height[rank[i]],查询任意两后缀的最长公共前缀就是求min(height[j])rank[j]在这两个后缀的rank直接,就可以rmq做了。。

由于这个貌似有太多看了代码才理解的了的地方,我只能讲成这样了TAT。。

UOJ 35

#include<cstdio>
#include<iostream>
#include<cstring>
#define N 200005
using namespace std;
int i,j,n,rank[N],sa[N],r2[N],buc[N],sec[N],h[N];
char s[N];
bool cmp(int x,int y,int d){return r2[x]==r2[y]&&r2[x+d]==r2[y+d];}
void getsa()
{
	int i,p=0,d=1;
	for (i=0;i<=n;i++) buc[i]=0;
	for (i=0;i<n;i++) buc[rank[i]=(int)s[i]]++;
	for (i=1;i<=256;i++) buc[i]+=buc[i-1];
	for (i=n-1;i>=0;i--) sa[--buc[rank[i]]]=i;
	
	while (p<n)
	{
		for (i=0;i<n;i++) r2[i]=rank[i],buc[i+1]=0;
		for (p=0,i=n-d;i<n;i++) sec[p++]=i;
		for (i=0;i<n;i++) if (sa[i]>=d) sec[p++]=sa[i]-d;
		for (i=0;i<n;i++) buc[rank[sec[i]]]++;
		for (i=1;i<=n;i++) buc[i]+=buc[i-1];
		for (i=n-1;i>=0;i--) sa[--buc[rank[sec[i]]]]=sec[i];
		for (rank[sa[0]]=1,i=1,p=1;i<n;i++) 
			rank[sa[i]]=cmp(sa[i],sa[i-1],d)? p:++p;
		d*=2;
	}
}
void geth()
{
	int i,j,k;
	for (i=0,k=0;i<n;i++)
		if (rank[i]==1) h[1]=0;
		else
		{
			j=sa[rank[i]-2];
			while (s[j+k]==s[i+k]) k++;
			h[rank[i]]=k;if (k) k--;
		} 
}
int main()
{
	scanf("%s",s);
	n=strlen(s);
	for (i=n;i<2*n;i++) s[i]='$';
	getsa();
	if (n==1) rank[0]=1;
	geth();
	for (i=0;i<n;i++) printf("%d ",sa[i]+1);printf("\n");
	for (i=2;i<=n;i++) printf("%d ",h[i]);
}


  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值