2020牛客暑期多校训练营(第一场)A. B-Suffix Array

链接

https://ac.nowcoder.com/acm/contest/5666/A

题意

对于字符串 t 1 t 2 . . . t k t_1t_2...t_k t1t2...tk

定义 B ( t 1 t 2 . . . t k ) = b 1 b 2 . . . b k B(t_1t_2...t_k)=b_1b_2...b_k B(t1t2...tk)=b1b2...bk

若存在 t j = t i t_j=t_i tj=ti ( j < i j<i j<i),那么 b i = min ⁡ 1 ≤ j < i , t j = t i { i − j } b_i=\min_{1\le j<i,t_j=t_i}\{i-j\} bi=min1j<i,tj=ti{ij}

否则 b i = 0 b_i=0 bi=0

将字符串 S S S (由 “a”,“b” 组成)的所有后缀按照其对应的 B B B 序列的字典序排序

思路

对于字符串 T T T,可发现 B ( T ) = A D , A = 01..10 B(T)=AD,A=01..10 B(T)=AD,A=01..10,可发现 A A A 的长度越大, A A A 的字典序越大

我们可以 O ( n ) O(n) O(n) 求出 B ( s 1 s 2 . . . s k ) B(s_1s_2...s_k) B(s1s2...sk) 所有的 A A A 的长度,易发现 B ( s 1 s 2 . . . s k ) B(s_1s_2...s_k) B(s1s2...sk) D D D B ( S ) B(S) B(S) 的后缀,用 S A SA SA B ( S ) B(S) B(S) 后缀的排序,可以将 A A A 作为第一关键字排序, D D D 作为第二关键字排序

注意:

对于类似 ‘‘aaa’’ 的情况,我们需要在最后加一个 ‘b’,此时 A = 0110 A=0110 A=0110,否则 A = 011 A=011 A=011,在 A A A 长度相等情况下, 011 011 011 明显是大于 010 010 010

在第二关键字排序时, D D D 可能为空,下标为 l e n + 1 , l e n + 2 len+1,len+2 len+1,len+2,分别将排名赋值为 0 , − 1 0,-1 0,1 即可

官方题解

C i = min ⁡ j > i , s j = s i { j − i } C_i = \min_{j > i,s_j = s_i} \{j - i\} Ci=minj>i,sj=si{ji},数组 C C C 的后缀从小到大排序就是答案

Detailed proof can be found in “Parameterized Suffix Arrays for Binary Strings ” http://www.stringology.org/event/2008/p08.html

代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
char s[N];
int n,nxt[2],pre[2],a[N],b[N],res[N];
int m,p,sa[N],id[N],rk[N],oldrk[N<<1],cnt[N];
void init() {
    for(int i=0;i<2;i++) nxt[i]=pre[i]=0;
    memset(b,0,sizeof b);
    for(int i=1;i<=n;i++) res[i]=i,a[i]=0;
}
void get_sa(int *s) {
    m=300;
    for(int i=1;i<=n;i++) cnt[i]=0;
    for(int i=1;i<=n;i++) cnt[rk[i]=s[i]]++;
    for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
    for(int i=n;i>=1;i--) sa[cnt[rk[i]]--]=i;
    for(int w=1,p;w<n;w<<=1,m=p) {
        p=0;
        for(int i=n;i>n-w;i--) id[++p]=i;
        for(int i=1;i<=n;i++) if(sa[i]>w) id[++p]=sa[i]-w;
        for(int i=1;i<=m;i++) cnt[i]=0;
        for(int i=1;i<=n;i++) cnt[rk[id[i]]]++;
        for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
        for(int i=n;i>=1;i--) sa[cnt[rk[id[i]]]--]=id[i];
        for(int i=1;i<=n;i++) oldrk[i]=rk[i];
        p=0;
        for(int i=1;i<=n;i++) rk[sa[i]]=((oldrk[sa[i]]==oldrk[sa[i-1]]&&oldrk[sa[i]+w]==oldrk[sa[i-1]+w])?p:++p);
    }
}
bool cmp(int x,int y) {
    return a[x]<a[y]||a[x]==a[y]&&rk[x+a[x]]<rk[y+a[y]];
}
int main() {
    while(~scanf("%d",&n)) {
        scanf("%s",s+1);
        init();
        for(int i=1;i<=n;i++) {
            if(pre[s[i]-'a']) b[i]=i-pre[s[i]-'a'];
            pre[s[i]-'a']=i;
        }
        for(int i=n;i>=1;i--) {
            if(!nxt[(s[i]-'a')^1]) a[i]=n-i+2;
            else a[i]=nxt[(s[i]-'a')^1]-i+1;
            nxt[s[i]-'a']=i;
        }
        get_sa(b);
        rk[n+1]=0;
        rk[n+2]=-1;
        sort(res+1,res+1+n,cmp);
        for(int i=1;i<=n;i++) printf("%d ",res[i]);
        puts("");
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值