后缀数组

引子

遇到了这道题目

双指针迅速切掉以后

来到这道题目

本题和 2007 年 12 月月赛金组同名题目 在题意上一致,唯一的差别是数据范围。

同样一份代码提交后:

100->84

打开题解一看

后缀数组……

进入正题

来自 IOI2009 国家集训队论文 后缀数组 罗穗骞:

后缀数组是处理字符串的有力工具。


定义\(rank\)数组(简称\(rk\)),\(rk[i]\) 为从\(i\)开始的后缀在所有后缀中的排名

定义\(sa\)数组,\(sa[i]\) 为排名为\(i\)的后缀的开头位置

简单的说,后缀数组是“排第几的是谁?”,名次数组是“你排第几?”。

容易看出:\(rk\) 数组和 \(sa\) 数组可以互相推导

\(sa[rk[i]]=i\)

\(rk[sa[i]]=i\)

难点

如何将后缀排序

倍增!

每次对长度为\(2^k\)的字符串进行排序

\(k=0\)时 单个字符按字典序排序

如何排序呢?

基数排序!

\(len=2^k\)

经典例子:

\(AABAAAAB\)

\(len=1\) 时:

\(A\) \(A\) \(B\) \(A\) \(A\) \(A\) \(A\) \(B\) 按字典序排序

\(len=2\)

\(AA\) \(AB\) \(BA\) \(AA\) \(AA\) \(AA\) \(AB\) 如何排序呢?

基数排序来啦

我们已经知道前 \(2^{k-1}\) 位组成的字符串和后 \(2^{k-1}\) 位组成的字符串的排名了(当\(len=2^{k-1}\)时算出的排名)

所以直接基数排序(双关键字排序)

具体实现

定义一个结构体

struct node
{
	int x,y;//x为第一关键字,y为第二关键字
	int id;//记录是第几个字符串
}a[maxn],b[maxn];//两个数组排序

然后,核心代码:

void getrk()
{
	for(register int i=1;i<=len;i++)
		cnt[s[i]]=1;
	for(register int i=1;i<=128;i++)
		cnt[i]+=cnt[i-1];
	for(register int i=1;i<=len;i++)
		rk[i]=cnt[s[i]];
	for(register int l=1;l<len;l*=2)
	{
		for(register int i=1;i<=len;i++)
			a[i].x=rk[i],a[i].y=rk[i+l],a[i].id=i;
		
		memset(cnt,0,sizeof(cnt));
		for(register int i=1;i<=len;i++)
			cnt[a[i].y]++;
		for(register int i=1;i<=len;i++)
			cnt[i]+=cnt[i-1];
		for(register int i=1;i<=len;i++)
			b[cnt[a[i].y]--]=a[i];
		
		memset(cnt,0,sizeof(cnt));
		for(register int i=1;i<=len;i++)
			cnt[a[i].x]++;	
		for(register int i=1;i<=len;i++)
			cnt[i]+=cnt[i-1];
		for(register int i=len;i>=1;i--)
			a[cnt[b[i].x]--]=b[i];
		for(register int i=1;i<=len;i++)
			if(a[i].x==a[i-1].x&&a[i].y==a[i-1].y)
				rk[a[i].id]=rk[a[i-1].id];
			else
				rk[a[i].id]=rk[a[i-1].id]+1;
	}
	for(register int i=1;i<=len;i++)
		sa[rk[i]]=i;
}

\(sa\)数组:通过 \(sa[rk[i]]=i\) ,就可以推导出sa数组了

题目链接: 【模板】后缀排序

好题

来一道好题: [USACO07DEC]Best Cow Line G就是上面那道

解法:

将原字符串翻转后接在原字符串上,中间用一个不可能出现的字符代替,求一遍 \(rk\) 数组(为了计算出每个后缀和前缀的顺序),相当于将这个字符串的前缀和后缀的顺序算了一遍,当遇到第一个字符和最后一个字符相同时:看看是前缀的字典序小还是后缀的字典序小就行了

核心代码:

	while(st<ed)
	{
		cnt++;
		if(s[st]<s[ed])
		{
			printf("%c",s[st]);
			st++;
		}
		else if(s[st]>s[ed])
		{
			printf("%c",s[ed]);
			ed--;
		}
        else
	    {
            if(rk[st]<rk[len-ed+1])
				printf("%c",s[st++]);
            else 
				printf("%c",s[ed--]);
        }
        if(cnt%80==0)
            printf("\n");
	}

后缀数组应用

典例:快速求取两个后缀间最长公共前缀。

这显然可以二分哈希,但是后缀数组不需要哈希且不会被卡。

咕咕咕

posted @ 2022-11-09 19:46  Y2y7m  阅读(33)  评论(0)    收藏  举报