[BZOJ1031]-[JSOI2007]字符加密Cipher-后缀数组模板(附自己的理解)

说在前面

之前写后缀数组都是在背板子,而且已经很久没碰过这玩意了….忘得一干二净w
原计划今天下午开始回血,没料到AC自动机的好题实在是太多了,不得不去写一遍,回血计划被迫推迟到了晚上=A=
不得不说即使是现在,看后缀数组仍然是一个很有难度的东西啊


题目

BZOJ1031传送门
懒得粘题面了…
不是权限题,点进去就可以看


解法

(重点不在这里)
会后缀数组基本就是一眼题了= =
把这个字符串复制一遍接到它本身后面去,这样就相当于是一个环的样子。把后缀数组算出来,然后输出对应位置的字符即可。那些SA[i]比原长度大的直接忽略就好(因为它们的开头在第二圈)


下面是自带大常数的代码

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

int len , N ;
int tp[200005] , tax[200005] , RA[200005] , SA[200005] ;
char ss[200005] ;

bool equ( int *f , int x , int y , int len ){
    int xl = ( x + len > N ? 0 : f[x+len] ) ,
        yl = ( y + len > N ? 0 : f[y+len] ) ;
    return f[x] == f[y] && xl == yl ;
}

void Rsort( int m ){
    for( int i = 1 ; i <= m ; i ++ ) tax[i] = 0 ;
    for( int i = 1 ; i <= N ; i ++ ) tax[ RA[tp[i]] ] ++ ;
    for( int i = 1 ; i <= m ; i ++ ) tax[i] += tax[i-1] ;
    for( int i = N ; i ; i -- ) SA[ tax[RA[tp[i]]]-- ] = tp[i] ;
}

void getSA(){
    for( int i = 1 ; i <= N ; i ++ )
        RA[i] = ss[i] , tp[i] = i ;
    int m = 127 ; Rsort( m ) ; //第一遍排序相当于预处理
    //也就是处理出二元组中前半部分的名次,相同的位置靠前名次也靠前
    //例如abcbac中,两个a的SA下标是1和2,而b是3和4,c是5和6
    for( int w = 1 , p ; p != N ; w += w , m = p ){
        p = 0 ;
        for( int i = N - w + 1 ; i <= N ; i ++ ) tp[++p] = i ;
        for( int i = 1 ; i <= N ; i ++ )//处理二元组排第几的是谁
            if( SA[i] > w ) tp[++p] = SA[i] - w ;//当SA小于等于w时,无法作为二元组
            //原来排第i的是SA[i],那么除了那些没有二元组的,现在二元组排第p的就是SA[i]-w.(SA[i]-w)是first的位置
        Rsort( m ) ; memcpy( tp , RA , sizeof( RA ) ) ;
        RA[SA[1]] = p = 1 ;
        for( int i = 2 ; i <= N ; i ++ )
            RA[ SA[i] ] = ( equ( tp , SA[i] , SA[i-1] , w ) ? p : ++ p ) ;
    }
}

void solve(){
    getSA() ;
    for( int i = 1 ; i <= N ; i ++ )
        if( SA[i] <= len ) printf( "%c" , ss[SA[i]+len-1] ) ;
}

int main(){
    scanf( "%s" , ss + 1 ) ;
    len = strlen( ss + 1 ) ; N = ( len << 1 ) ;
    for( int i = len + 1 ; i <= N ; i ++ )
        ss[i] = ss[i-len] ;
    solve() ;
}

一点理解

其实个人觉得那个「五分钟学会XXX」的博客讲的已经很清楚了
SA[i]表示的是后缀排名第i的是以SA[i]开始的后缀
RA[i]表示以i开始的后缀排名为RA[i]

在求后缀数组之前需要预处理二元组中的first的排名,也就是代码里for循环外面的那个Rsort
里面用于Rsort的tp数组就是在记录二元组的second的信息。Rsort函数里累加桶的时候还是根据first来累加的,然后用second来判断准确的SA是什么。

注意RA数组里的数字代表的是当前first的种类,因此RA数组只有在后缀数组计算完毕时,才包含了1到len的数字。而SA数组一开始就包含了1到len的所有数字,仅仅是因为不可能SA[i]又等于j又等于k(毕竟在排序完成之前,j和k的后缀排名可能都为i),所以为了方便才让SA[i]=j而SA[i+1]=k。
但是这里就要注意,如果一开始规定相同first靠后的编号大,那么就要一直保持这个性质,而不能一会靠前编号大一会靠后编号大,这样在形如「abababaaaaaaaaaaaa」这样的数据下就会GG,这也就是「 for( int i = N - w + 1 ; i <= N ; i ++ ) tp[++p] = i ; 」的for顺序不能倒过来写的原因

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值