2020牛客多校第一场 B题Suffix Array(结论+后缀数组)
2020牛客多校第一场 B题Suffix Array(结论+后缀数组)
刚学后缀数组有点菜没a出来,看题解后,是个结论没发现:
• Let C_i = min_{j > i and s_j = s_i} {j - i}
• The B-Suffix Array is equivalent to the suffix array of C_1 C_2 ... C_n
这是题解的原话,我给大家翻译一下,c[i]的值是索引i后面离它最近的与s[i]同字符的距离,然后字符串的后缀数组就与这个c数组的后缀数组,这个结论只适用于只有两个字符的情况,所以题目字符也只有两种a与b,怎么证明的话大家去看官方证明吧,本人过菜,我是看半天没看明白,orz。
不过我可以给大家演示一下。(注意如果是最后的a,b的话它后面无a,b那么结果是无穷大,我这边就让他等于n了,最后一位让它加一位为n+1)。
如abaab的c数组为231556,列出c的后缀为:
231556,31556,1556,556,56,6
所以,sa数组就是312456,反过来54213就是答案了。
我的写法是把231556变成32400-1,这样比较契合我的写法,不用大改cmp。
代码,之前看过其他大佬的,都写得很多,好像不是这么做的,可能是自己想出来的神仙写法吧orz。
还有我的后缀数组板子的话用的是书上的,比大家的短很多,主要我刚学,也不清楚他们那种是不是有什么优化,希望大佬指点迷津,以下是ac代码。
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int n,c[100007];
string s;
int ran[100007],tmp[100007],sa[100007],k;
void init(){
k=0;
memset(ran,0,sizeof(ran));
memset(tmp,0,sizeof(tmp));
memset(c,0,sizeof(c));
}
bool cmp(int i,int j){
if(ran[i]!=ran[j]) return ran[i]<ran[j];
else{
int ri,rj;
if(i+k<=n){
ri=ran[i+k];
}
else{
ri=-1;
}
if(j+k<=n){
rj=ran[j+k];
}
else{
rj=-1;
}
return ri<rj;
}
}
void get_sa(int sa[]){
for(int i=0;i<=n;i++){
sa[i]=i;
if(i<n){
ran[i]=c[i];
}
else{
ran[i]=-1;
}
}
for(k=1;k<=n;k*=2){
sort(sa,sa+n+1,cmp);
tmp[sa[0]]=0;
for(int i=1;i<=n;i++){
tmp[sa[i]]=tmp[sa[i-1]]+(cmp(sa[i-1],sa[i]) ? 1 : 0);
}
for(int i=0;i<=n;i++){
ran[i]=tmp[i];
}
}
}
int main(){
while(~scanf("%d",&n)){
cin>>s;
int a=n,b=n;
for(int i=n-1;i>=0;i--){
if(s[i]=='a'){
if(a==n)c[i]=(n-a)%n;
else c[i]=(n-a+i)%n;
a=i;
}
else{
if(b==n)c[i]=(n-b)%n;
else c[i]=(n-b+i)%n;
b=i;
}
}
get_sa(sa);
for(int i=1;i<=n;i++){
printf("%d ",sa[i]+1);
}puts("");
}
}