后缀数组 模板【JSOI2007】 bzoj1031 字符加密Cipher

21 篇文章 0 订阅
4 篇文章 0 订阅

后缀数组
处理多个字符串用AC自动机就很方便了,但是AC自动机需要空间巨大,很可能爆内存,所以后缀数组被引入了。

后缀数组主要是两个数组
第一个是sa数组,sa[i]代表排名为i的后缀的首字母的位置;
第二个是rnk数组,rnk[i]代表首字母位置为i的后缀的排名。

最先要做的就是求出这两个数组。
其实有很简单的办法,那就是把后缀sort一下,排个序,统计一下排名,万事大吉。
如果时间复杂度允许的话……

还是用另一种方法吧:倍增+基数排序
先把每个后缀按照首字母排序,按照首字母给每个后缀一个名次;
再把每个后缀的排名当做权值,把相邻的两个结合成一个二元组(后面没有元素就用0占位),再进行排序,这样又得到一个新的名次;
之后用倍增的思想,把当前位置与后面第二个的排名结合成一个二元组,再排序;
然后把当前位置与后面第四个的排名结合成一个二元组,再排序;
……
直到没有相同的排名(只要连接的长度大于等于字符串的长度,就可以保证排名一定互不相同了)。
时间复杂度O(nlogn)

注意事项:
1、在每次基数排序时要把sum(桶数组)数组清空;
2、按照关键字排序要先排第二位再排第一位;
3、如果后面没有元素要用0占位;
4、每次sa数组要按顺序赋值。
后缀数组模板代码如下:
(如果该代码与其他人的代码雷同,纯属……没错,我就是照PoPoQQQ大神抄的)

void get_rank()
{
    for(int i=1;i<=tot;i++) sum[s[i]]++;
    for(int i=1;i<=127;i++) sum[i]+=sum[i-1];
    for(int i=tot;i>=1;i--) tmp[sum[s[i]]--]=i;
    for(int i=1;i<=tot;i++)
    {
        if(i==1 || s[tmp[i]]!=s[tmp[i-1]]) t++;
        rnk[tmp[i]]=t;
    }
}
void radix_sort(int key[],int order[])
{
    for(int i=0;i<=tot;i++) sum[i]=0;
    for(int i=1;i<=tot;i++) sum[key[i]]++;
    for(int i=1;i<=tot;i++) sum[i]+=sum[i-1];
    for(int i=tot;i>=1;i--) tmp[sum[key[order[i]]]--]=order[i];
    for(int i=1;i<=tot;i++) order[i]=tmp[i];

}
void suffix_array()
{
    get_rank();
    for(int j=1;j<=tot;j<<=1)
    {
        for(int i=1;i<=tot;i++)
        {
            X[i]=rnk[i];
            Y[i]=i+j>tot?0:rnk[i+j];
            sa[i]=i;
        }
        radix_sort(Y,sa);
        radix_sort(X,sa);
        t=0;
        for(int i=1;i<=tot;i++)
        {
            if(i==1 || X[sa[i]]!=X[sa[i-1]] || Y[sa[i]]!=Y[sa[i-1]]) t++;
            rnk[sa[i]]=t;
        }
    }
}

【JSOI2007 字符加密Cipher】bzoj1031
题目大意:
给一个字符串,把这个字符串变成环,然后以每个字母为首字母沿着环形成一个新的字符串,按照字典序排序,然后依次输出尾字母。

题目分析:
处理环的问题,我们常用的方法就是把原串复制一遍接在后面。
这道题就可以把原串接在后面然后用后缀数组排序,只取首字母在第一个串里的后缀,按sa数组的顺序输出尾字母。

主函数代码如下:

int main()
{
    scanf("%s",s+1);
    tot=strlen(s+1);
    for(int i=1;i<=tot;i++) s[tot+i]=s[i];
    tot*=2;
    suffix_array();
    tot/=2;
    for(int i=1;i<=tot*2;i++)
    {
        if(sa[i]>tot) continue;
        printf("%c",s[sa[i]+tot-1]);
    }
    printf("\n");
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值