zoukankan      html  css  js  c++  java
  • 后缀数组

    先说后缀是啥:后缀就是一个串s从第s[i]一直到串尾就是这个串的一个后缀,记作suffix[i]

    举个栗子:aacbds的后缀分别为aacbds,acbds,cbds,ds,ds,s(这是回音你懂吗 是回音你懂吗 回音你懂吗 音你懂吗 你懂吗 懂吗 吗)

    后缀数组实质就是给每个后缀排个序

    比如上面内个串(aacbds,不是回音内个),它的后缀数组就是{1,2,4,3,5,6}

    把每个后缀一个一个拿出来排太慢,会T掉,这个时候就要用到强大的倍增算法

    倍增求后缀数组:

    本质就是从1开始倍增一个l,把rank[i]和rank[i+l]看做是一个长度为2的串(如果i+l>=n呢么rank[i+l]=0),给这个长度为2的串排下序,在根据这次拍的序继续倍增然后继续排

    为什么呐,因为字符串排序是从前往后优先级逐渐递减,内个长度为2的串就相当于把一个串拆卡,拆开的部分的名次前面已经求出来了(因为倍增的长度依次*2,所以从中间拆两半拆出来的名次前面一定求过),而且内个长度为2的串是个串,所以优先级也是前面的大(如果看不懂可以自己画下图,或者直接看内个超级经典的图(基本上有关后缀数组的教程都有内个图,是国家集训队论文的图))

    排内个长度为2的串一般用桶排序,非常方便

     下面是加上注释的代码,我的代码缩行了,而且用了一些省略的写法,看不懂的同学可以去网上搜索其它版本的代码

     1 int n;  char s[1100];
     2 int rank[1100],height[1100];
     3 int cnt[1100],cnt_rank[1100];//
     4 int rank1[1100],rank2[1100];//要拼成长度为2的串的两个名次
     5 int SA[1100],temp_SA[1100];//表示rank[i]是从下标为几的字符开始的
     6 void get_suffix_rank(){
     7     memset(cnt,0,sizeof(cnt));
     8     for(int i=0;i<n;i++)  cnt[s[i]]++;//计数
     9     for(int i=1;i<=210;i++)  cnt[i]+=cnt[i-1];//把名次堆起来
    10     //因为这个版本的名次是堆的,所以a,a,b,b,c的名次分别是1,1,3,3,4
    11     for(int i=0;i<n;i++)  rank[i]=cnt[s[i]]-1;//因为搞了个'$'所以要-1让'$'的名次为0
    12     for(int l=1;l<n;l*=2){//倍增长度
    13         for(int i=0;i<n;i++){  rank1[i]=rank[i];  rank2[i]=(i+l<n) ? rank[i+l] : 0;}//两个名次拼成一个长度为2的串
    14         //下面是基数排序,因为长度为2所以直接分成两个写
    15         memset(cnt_rank,0,sizeof(cnt_rank));
    16         for(int i=0;i<n;i++)  cnt_rank[rank2[i]]++;
    17         for(int i=1;i<n;i++)  cnt_rank[i]+=cnt_rank[i-1];
    18         for(int i=n-1;i>=0;i--)  temp_SA[--cnt_rank[rank2[i]]]=i;//注意这里是n-1到0
    19         memset(cnt_rank,0,sizeof(cnt_rank));
    20         for(int i=0;i<n;i++)  cnt_rank[rank1[i]]++;
    21         for(int i=1;i<n;i++)  cnt_rank[i]+=cnt_rank[i-1];
    22         for(int i=n-1;i>=0;i--)  SA[--cnt_rank[rank1[temp_SA[i]]]]=temp_SA[i];//和上一个相比就是把i换成了temp_SA
    23         rank[SA[0]]=0;
    24         for(int i=1;i<n;i++){
    25             rank[SA[i]]=rank[SA[i-1]];
    26             rank[SA[i]]+=(rank1[SA[i]]!=rank1[SA[i-1]] || rank2[SA[i]]!=rank2[SA[i-1]]);
    27         }
    28     }
    29 }
    30 
    31 int main(){//freopen("ddd.in","r",stdin);
    32     scanf("%s",&s);  n=strlen(s);  s[n++]='$';//最后要搞个这个东西,防止在倍增过程中出现重复的(比如说a要比a$小)
    View Code

    还有一种叫做DC3的算法,复杂度更优,然而我并不会一。一,有兴趣的同学可以去网上搜索,在OI里倍增一般够用了,而且倍增简单得多 

    然而只是求出后缀数组一般没什么卵用,更多的时候要用到一个叫做height的非常强大的数组

    height里面存的是相邻两个后缀的LCP,即最长公共前缀

    怎么求呐:

    你也可以把每相邻两个后缀从头比较,不会酱紫太慢,会T掉

    可以像kmp那样搞,根据前面求出来的height直接跳过不用对比的地方,直接从上一个height开始比

    怎么证明我太弱不会,如果哪天我懂了会来补上去

    这是代码:

    1 void get_height(){
    2     int _l=0;
    3     for(int i=0;i<n;i++)if(rank[i]){
    4         int j=SA[rank[i]-1];
    5         while(i+_l<n && j+_l<n && s[i+_l]==s[j+_l])  _l++;
    6         height[rank[i]]=_l;
    7         _l-=(_l>0);//因为往后推了一位,所以要-1
    8     }
    9 }
    View Code

    后缀数组还有很多很棒的应用,这个会另开一篇

    刚学后缀数组的时候会觉得很难,然而多看几遍,照着标程写的同时一步一步理解,时间长了后缀数组就和线段树一样不过是随手写出来的小工具而已

  • 相关阅读:
    https://blog.csdn.net/yongchaocsdn/article/details/53355296
    P1526 [NOI2003]智破连环阵 [搜索+剪枝(二分图)]
    AT2165 Median Pyramid Hard [二分答案]
    翻煎饼 [迭代加深搜索+剪枝]
    P2962 [USACO09NOV]灯Lights [高斯消元+异或方程组 / 折半搜索]
    P5025 [SNOI2017]炸弹 [线段树优化建图 + Tarjan]
    Tarjan [割点, 缩点, 桥(待填坑)]
    线段树优化建图学习笔记
    P5468 [NOI2019]回家路线 [斜率优化dp]
    CF573E Bear and Bowling [平衡树+动态规划]
  • 原文地址:https://www.cnblogs.com/JSL2018/p/5834520.html
Copyright © 2011-2022 走看看