2020牛客多校(一)A题 B-Suffix Array(后缀数组)
题目的含义是将一个字符串的所有后缀按算出来的B函数从小到大按字典序排序
首先观察到题目给定的只有ab两个字符,并且b函数给的是与当前位置之前的最近的相同的字符的位置差值
首先暴力的思想就是对每个后缀算一遍b函数,但是发现这样是超时的,因此考虑能否进行优化
我们观察到B函数会变化的原因是,我们求到某个后缀时,前面的数字是不存在的,因此当前位需要重0开始算
但是一旦后缀中出现过a和b这两个字符,这样的话,这个后缀往后的b函数就和初始字符串是一样的。
因此我们想到,如果可以分两部分进行排序,就可以解决问题,因为后一部分只需要求一次即可。
而前一部分,只可能包含类似aaaab,bbba这种,当两种字符都出现过就到了第二部分
而这种类似的字符串的b函数就是01110,这样两个0之间包含很多1。只需要比长度即可,而后半部分用后缀数组求一下rk值即可
当然有些特殊情况,例如字符串后缀是bbbb这样只出现一种字符,我们只需要在n+1的位置补一下a就行,这并不影响原先的排名。
#include<bits/stdc++.h> using namespace std; const int N=2e6+10; int od[N],rk[N],id[N],cnt[N],sa[N],px[N]; char s[N]; int b[N]; struct node{ int x,y; bool operator <(const node &a) const{ if(y-x==a.y-a.x){ return rk[y+1]<rk[a.y+1]; } return y-x<a.y-a.x; } }res[N]; bool cmp(int x,int y,int w){ return od[x]==od[y]&&od[x+w]==od[y+w]; } void da(int *s,int n,int m){ int i; for(i=1;i<=n;i++) 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; int p; for(int w=1;w<n;w<<=1,m=p){ p=0; for(i=n;i>n-w;i--) id[++p]=i; for(i=1;i<=n;i++) if(sa[i]>w) id[++p]=sa[i]-w; for(i=1;i<=n;i++) 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]; for(i=0;i<=n;i++) od[i]=rk[i]; for(p=0,i=1;i<=n;i++){ rk[sa[i]]=cmp(sa[i-1],sa[i],w)?p:++p; } } } int main(){ int n; ios::sync_with_stdio(false); while(cin>>n){ cin>>s+1; int x=-1; int y=-1; int i; for(i=1;i<=n;i++){ b[i]=0; if(s[i]=='a'){ if(x!=-1) b[i]=i-x; x=i; } else{ if(y!=-1) b[i]=i-y; y=i; } } da(b,n,n); x=n+1,y=n+1; for(i=n;i>=1;i--){ if(s[i]=='a'){ res[i]={i,y}; x=i; } else{ res[i]={i,x}; y=i; } } rk[n+1]=-1; rk[n+2]=-2; sort(res+1,res+1+n); for(i=1;i<=n;i++){ cout<<res[i].x<<" "; } cout<<endl; } }
没有人不辛苦,只有人不喊疼