牛客多校第一场 A-B-Suffix Array(后缀数组)

题意

  • 链接:B-Suffix Array
  • 给你一个长度为n的只由‘a’和‘b’组成字符串(1<=n<=105),并重新定义了这个字符串的所有后缀串的值,求重新定义完以后的该字符串的所有后缀的sa,定义如下:当前位置的值为与前一次出现相同字符的位置的距离。

解题思路

举个例子:abaabaaaba,则这个字符串的后缀与所对应的值分别为:

后缀所对应的值AB
abaabaaaba00213211420021321142
baabaaaba001321142001321142
aabaaaba0102114201021142
abaaaba00211420021142
baaaba001142001142
aaaba0110201102
aaba01020102
aba002002
ba0000null
a00null

根据表格前两列我们可以找到规律并把字符串的每个后缀所对应的值分成A、B两部分如表格的三四列所示

  • 首先盯着A看,你会发现一个神奇的事情就是,字符串A越长(后缀串中第一个a与第一个b出现的距离),这个字符串A的字典序就越大,即A对应的后缀越大!
  • 然后盯着B看,你会发现这些都是原始字符串的第n−len+1个后缀(len是B的长度),因此我们对原始数组求一次 正常的(未按题目定义的) sa即可得到后面一部分字符串的rk。
  • 怎么求sa呢可以看看这里

那么我们可以根据A来对字符串的每个后缀进行第一步排序,然后对相同的A的后缀进行根据B内部排序即可

代码

#include<stdio.h>
#include<algorithm>
using namespace std;
int const maxn=1e5+6;
int sa[maxn],rk[maxn],tp[maxn],tax[maxn];
int n,m,p,ss[maxn];
char s[maxn];
void radixsort()
{
	for(int i=0;i<=m;i++)tax[i]=0; 
	for(int i=1;i<=n;i++)tax[rk[i]]++;
	for(int i=1;i<=m;i++)tax[i]+=tax[i-1];
	for(int i=n;i>=1;i--)sa[tax[rk[tp[i]]]--]=tp[i];
} 
void getsa()
{
	m=n;
	for(int i=1;i<=n;i++) rk[i]=ss[i],tp[i]=i;
	radixsort();
	for(int k=1;k<=n;k<<=1)
	{
		p=0;
		for(int i=n-k+1;i<=n;i++)tp[++p]=i;		
		for(int i=1;i<=n;i++)
			if(sa[i]>k)tp[++p]=sa[i]-k;
		radixsort();
		for(int i=1;i<=n;i++)tp[i]=rk[i];//用swap会超时 
		rk[sa[1]]=p=1;
		for(int i=2;i<=n;i++)
			if(tp[sa[i]]==tp[sa[i-1]]&&tp[sa[i]+k]==tp[sa[i-1]+k])
				rk[sa[i]]=p;
			else{
				p++;rk[sa[i]]=p;
			} 
		if(p==n)break;
		m=p;
	}
}
struct node{
	int x,y;
}a[maxn];
bool cmp(node a,node b)
{
	if(a.y-a.x==b.y-b.x)return rk[a.y+1]<rk[b.y+1];
	else return a.y-a.x<b.y-b.x;
}
int main()
{
	while(~scanf("%d",&n))
	{
		scanf("%s",s+1);
		int x=-1,y=-1;
		for(int i=1;i<=n;i++)//按定义转译原字符串s 
		{
			ss[i]=0;
			if(s[i]=='a')
			{
				if(x!=-1)ss[i]=i-x;
				x=i;//更新a最新出现的位置 
			}
			else if(s[i]=='b')
			{
				if(y!=-1)ss[i]=i-y;
				y=i;//更新b最新出现的位置 
			}
			//ss[i]++;
		}
		getsa();
		rk[n+1]=-1;
		rk[n+2]=-2;
		x=y=n+1;
		for(int i=n;i>=1;i--)
		{
			if(s[i]=='a')
			{
				a[i].x=i;a[i].y=y;
				x=i;
			}
			else if(s[i]=='b'){
				a[i].x=i;a[i].y=x;
				y=i;			
			}
		}
		sort(a+1,a+1+n,cmp);
		for(int i=1;i<=n;i++)printf("%d ",a[i].x);
		printf("\n");
	}
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值