• 浅谈后缀数组SA


    这篇博客不打算讲多么详细,网上关于后缀数组的blog比我讲的好多了,这一篇博客我是为自己加深印象写的。

    给你们分享了那么多,容我自私一回吧~

    参考资料:这位dalao的blog

    一、关于求SuffixArray的一些变量定义:

    1. sa[i]=j,表示第i名的后缀从j开始

    **存的是下标**

    2. rnk[i]=j,从i开始的后缀是第j名的

    **与sa为互逆运算,存的是值**

    3. tp[i]=j, 第二关键字为i的后缀从j开始

    **可理解为第二关键字的SA,存的是下标**

    插入解释一下第一关键字和第二关键字:

    我们要对所有的后缀进行排序,怎么排呢?

    开始时,我们每个字符的后缀存的只有它自己,所以它后缀的大小就是它的ASCII码。

    我们把每个字符i看成(s[i],i)的二元组,如果我们直接丢pair<int,int>里面然后std::sort,

    这样的时间复杂度是O(log^2 n)的,显然不够优秀。

    所以就需要用到基数排序RadixSort,不了解的自行百度。

    再使用倍增法,就可以使我们排序的时间复杂度降低到O(logn)。

    所以我们要对每个后缀的前两个字母进行排序,第一个字母的相对关系已经得到了。

    第i个后缀的第二个字母,就是第i+1个后缀的第一个字母,利用这个关系我们第二个字母的相对关系也就知道了。

    我们的tp数组就是用来记录它的,rnk[i]表示上一轮中第i个后缀的排名。

    这里引用神仙attack的一句话,我觉得讲的非常到位:

    对于一个长度为w的后缀,你可以形象的理解为:

    第一关键字针对前w2个字符形成的字符串,第二关键字针对后w2个字符形成的字符串

    然后对每个后缀的前4个字母组成的字符串排序,前8个,前16个...这就是倍增法求SA的流程了。

    给出RadixSort的代码:

    void RadixSort(int a[],int b[]){//基数排序 
        for(int i=0;i<=m;i++)tax[i]=0;
        for(int i=1;i<=n;i++)tax[a[i]]++;
        for(int i=1;i<=m;i++)tax[i]+=tax[i-1];
        for(int i=n;i>=1;i--)sa[tax[a[b[i]]]--]=b[i];
    }

    实在不能理解RadixSort也没有关系,代码很短

    再给出求SA的代码:

    bool cmp(int *r,int a,int b,int k){
        return r[a]==r[b]&&r[a+k]==r[b+k];
    }
    void getSA(int a[],int b[]){
        for(int i=1;i<=n;i++)
            m=max(m,a[i]=s[i]-'0'),b[i]=i;
        RadixSort(a,b);
        for(int p=0,j=1;p<n;j<<=1,m=p){
            p=0;
            for(int i=1;i<=j;i++)b[++p]=n-j+i;
            for(int i=1;i<=n;i++)if(sa[i]>j)b[++p]=sa[i]-j;
            RadixSort(a,b);
            int *t=a;a=b;b=t;
            a[sa[1]]=p=1;
            for(int i=2;i<=n;i++)
                a[sa[i]]=cmp(b,sa[i],sa[i-1],j)?p:++p;
        }
    }

    关于代码的解释,有时间再填坑。本蒟蒻要学的算法还很多...SA就粗略地理解一下好了

    开始填坑,先补充一个东西叫height数组。

    height[i]表示排名为i的后缀和排名为i-1的后缀的最长公共前缀LCP。

    暴力求解时间复杂度是O(n^2),根据一个性质height[i+1]>=height[i]-1

    可以O(n)时间内求出height数组,具体代码:

    void getHeight(){
        for(int i=1,j=0;i<=n;i++){
            if(j)j--;
            while(s[i+j]==s[sa[rnk[i]-1]+j])j++;
            height[rnk[i]]=j;
        }
    }

     关于这个height数组,它可以干什么,给出一张列表:

    两个后缀的最大公共前缀

    lcp(x,y)=min(heigh[xy])lcp(x,y)=min(heigh[x−y]), 用rmq维护,O(1)查询

    可重叠最长重复子串

    height数组里的最大值

    不可重叠最长重复子串

    首先二分答案x,对height数组进行分组,保证每一组的最小height>=x

    依次枚举每一组,记录下最大和最小长度,若sa[max]sa[min]>=x那么可以更新答案

    本质不同的子串的数量

    枚举每一个后缀,第i个后缀对答案的贡献为lensa[i]+1height[i]

  • 相关阅读:
    a gcc 4.2.4 bug(被stos指令累加后%edi作为参数的)
    gcc -02引起内存溢出'unsigned i'应修订为'volatile unsigned i'
    gcc优化引起get_free_page比__get_free_page返回值多4096
    gcc请不要优化
    change_bit 按位取反
    IBM messed up *AGAIN* in their thinkpad: 0xA0000 -> 0x9F000
    python正则实例
    详解volatile 关键字与内存可见性
    并发基础知识
    Spring通过注释配置Bean2 关联关系
  • 原文地址:https://www.cnblogs.com/light-house/p/11784966.html
Copyright © 2020-2023  润新知