1. 程式人生 > 實用技巧 >2020牛客多校(一)A題 B-Suffix Array(字尾陣列)

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; } }
View Code