2020牛客多校(第一场)- A.B-Suffix Array

题目描述:

给定一个仅含字母 a a a b b b 的字符串 t t t ,并给出 B B B 数组的构造方法:

  1. 若存在 j < i   a n d   t i = t j jj<i and ti=tj ,则 B i = m i n 1 ≤ j < i , t i = t j { i − j } , B_i=min_{1\leq jBi=min1j<i,ti=tj{ij},
  2. 否则, b i = 0. b_i=0. bi=0.

现在要求对 t t t 的每一个后缀求一遍 B B B 数组,然后按字典序升序输出后缀的编号

分析:

随便想几个例子构造一下 B B B 数组,大致的样式都是 0 1 k 0.... 01_{k}0.... 01k0.... 即前部分是两个 0 0 0 之间有若干个 1 1 1 ,然后后部分皆大于 0 0 0;与普通后缀不同的是,前部分两个 0 0 0 的值在往前的时候是可能会改变的,而后部分的值往前不会再改变;
抓住这一点,可以先比较 0 1 k 0 01_{k}0 01k0 ,相等的话再比较后部分,而后部分显然求一遍后缀数组是最好的选择。前面的部分,越长字典序越大,所以只需要求两个 0 0 0 的位置,而这也不难求;
0 1 k 0 01_{k}0 01k0 对应的形式即 a a k b aa_kb aakb 或者 b b k a bb_ka bbka,所以对于当前位置,只需要找到后面第一个不相等字母的位置即可;

所以思路是基于字符串 t t t 求出完整的 B B B 数组求一遍后缀数组,然后找到每个后缀的前部分的长度;排序就是先比较前部分大小,相等再利用 r k rk rk 数组比较后部分的大小;

会有特例比如:

  1. a b ab ab 或者 b a ba ba ,代表的 B B B 数组是 00 ; 00; 00;
  2. a k a_k ak 或者 b k b_k bk ,代表的 B B B 数组是 0 1 k − 1 ; 01_{k-1}; 01k1;

对于情况 1 1 1 ,前部分相等的话, r k rk rk 数组的比较会延申到 r k [ n + 1 ] rk[n+1] rk[n+1];对于情况 2 2 2 ,我们假设第二个 0 0 0 的位置在 n + 1 n+1 n+1 ,前部分相等的话, r k rk rk 数组的比较会延申到 r k [ n + 2 ] rk[n+2] rk[n+2] ,所以相应的需要设 r k [ n + 1 ] = − 1 , r k [ n + 2 ] = − 2 rk[n+1]=-1,rk[n+2]=-2 rk[n+1]=1,rk[n+2]=2

代码:

#include
#include
#include 
using namespace std;
#define st first
#define nd second
#define P pair
#define ll long long
#define pb push_back
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define frep(i,a,b) for(int i=a;i>=b;i--) 
const int N = 1E6+10;


int n,sa[N],rk[N],oldrk[N<<1];
int ht[N],px[N],id[N],cnt[N];

bool cmp(int x,int y,int w){
	return oldrk[x]==oldrk[y] && oldrk[x+w]==oldrk[y+w];
}

void da(int s[],int n,int m){
	int i,p=0,w,k;
	rep(i,0,n) cnt[i]=0;

	for(i=1;i<=n;i++) ++cnt[rk[i] = s[i]];
	for(i=1;i<=m;i++) cnt[i] += cnt[i-1];
	for(i=n;i>=1;i--) sa[cnt[rk[i]]--] = i;
	
	for(w=1;w<n;w<<=1,m=p){
		for(p=0,i=n;i>n-w;i--) id[++p]=i;
		for(i=1;i<=n;i++)
		    if(sa[i]>w) id[++p]=sa[i]-w;
		rep(i,0,n) cnt[i]=0;
		for(i=1;i<=n;i++) ++cnt[px[i] = rk[id[i]]];
		for(i=1;i<=m;i++) cnt[i] += cnt[i-1];
		for(i=n;i>=1;i--) sa[cnt[px[i]]--] = id[i];
		rep(i,0,n) oldrk[i]=rk[i];
		for(p=0,i=1;i<=n;i++)
		    rk[sa[i]]=cmp(sa[i],sa[i-1],w)?p:++p;
	}
	
	for(i=1,k=0;i<=n;i++){
		if(k) --k;
		while(s[i+k]==s[sa[rk[i]-1]+k]) ++k;
		ht[rk[i]]=k;
	} 
}

char str[N];
int s[N]; 
P v[N];

bool cmp2(P a,P b)
{
	if(a.nd-a.st!=b.nd-b.st) return a.nd-a.st<b.nd-b.st;
	return rk[a.nd+1]<rk[b.nd+1];
}

int main()
{
    while(~scanf("%d",&n))
    {
    	scanf("%s",str+1);
        int a=-1,b=-1;
        frep(i,n,1)
        {
        	s[i]=0;
        	int c=str[i]-'a';
        	if(c==0){
        		if(a!=-1) s[a]=a-i;
				a=i; 
			}
        	else{
        		if(b!=-1) s[b]=b-i;
        		b=i;
			}
		}  // s 即完整的B数组
		da(s,n,n);  //求一遍后缀数组
		
		rk[n+1]=-1;
		rk[n+2]=-2; 
		a=b=n+1;
		frep(i,n,1)  //求出前部分的长度
		{
			int c=str[i]-'a';
			if(c==0){
				v[i]=P(i,b);
				a=i;
			}
			else{
				v[i]=P(i,a);
				b=i;
			}
		}
		sort(v+1,v+n+1,cmp2);
		rep(i,1,n) printf("%d%c",v[i].st,i==n?'\n':' ');
	}
}

你可能感兴趣的:(后缀数组,NKDX)