• (模板)倍增法求后缀数组


    题目链接:https://www.luogu.com.cn/problem/P3809

    题意:给定长为n的字符串s,将s的每个后缀字符串排序(升序),从小到大输出后缀字符串在s中出现的第一个位置。

    思路:

      今天碰到一个要用后缀数组的题,于是来洛谷刷模板题,搞了一整天终于明白了板子怎么写。

      参考大佬的博客:https://xminh.github.io/2018/02/27/%E5%90%8E%E7%BC%80%E6%95%B0%E7%BB%84-%E6%9C%80%E8%AF%A6%E7%BB%86(maybe)%E8%AE%B2%E8%A7%A3.html

      求sa数组的复杂度为O(nlogn)(博客中提到求sa数组还有一种O(n)的D3C算法,但比如倍增容易理解,代码较难实现,所以暂时只学倍增法),求height数组的复杂度为O(n)。

    AC code(含注释):

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    
    const int maxn=1000005;
    char s[maxn];
    int y[maxn],x[maxn],c[maxn],sa[maxn],rk[maxn],height[maxn];
    int n,m;
    
    //倍增+基数排序求sa,O(nlogn)
    void get_SA(){
        for(int i=1;i<=n;++i){
            x[i]=s[i];
            ++c[x[i]];
        }
        //c数组是桶,x[i]是第i个元素的第一关键字
        for(int i=2;i<=m;++i)
            c[i]+=c[i-1];
        //求c的前缀和,得到每个关键字最多在第几名
        for(int i=n;i>=1;--i)
            sa[c[x[i]]--]=i;
        for(int k=1;k<=n;k<<=1){
            int num=0;
            for(int i=n+1-k;i<=n;++i)
                y[++num]=i;
            //y[i]表示第二关键字排名为i的数,第一关键字的位置
            //第n-k+1到第n位没有第二关键字,排在最前面
            for(int i=1;i<=n;++i)
                if(sa[i]>k)
                    y[++num]=sa[i]-k;
            //排名为i的数,在树组中是否在第k位以后
            //如果满足,那么它可以作为别人的第二关键字
            //就把它的第一关键字的位置添加进y就行了
            //所以i枚举的是第二关键字的排名,第二关键字靠前的先入队
            for(int i=1;i<=m;++i) c[i]=0;
            //初始化桶c
            for(int i=1;i<=n;++i)
                ++c[x[i]];
            //因为上一次循环已经算出这次的第一关键字了,所以直接加就行
            for(int i=2;i<=m;++i)
                c[i]+=c[i-1];
            //求c的前缀和,得到每个关键字最多在多少名
            for(int i=n;i>=1;--i)
                sa[c[x[y[i]]]--]=y[i],y[i]=0;
            //因为y的顺序是按照第二关键字的顺序来排的
            //第二关键字靠后的,在同一第一关键字桶中排名越靠后
            //基数排序
            swap(x,y);
            x[sa[1]]=1,num=1;
            for(int i=2;i<=n;++i)
                x[sa[i]]=(y[sa[i]]==y[sa[i-1]] && y[sa[i]+k]==y[sa[i-1]+k])? num:++num;
            //因为sa[i]已经排好序了,所以可以按排名枚举,生成下一次循环的第一关键字
            if(num==n) break;
            //如果所有的序号都不一样,那么排序完毕
            m=num;
            //这里不用原来的122了,因为有新的编号了
        }
        for(int i=1;i<=n;++i){
            if(i!=1) printf(" ");
            printf("%d",sa[i]);
        }
    }
    
    //O(n)
    //height[i]=LCP(i,i-1)
    //LCP为suff(sa[i])与suff(sa[j])的最长公共前缀
    //即排在第i的字符串和排在第j的字符串的最长公共前缀
    //hi[i]=height[rk[i]]
    //即第i个字符串和排序后在它前面的字符串的最长公共前缀
    void get_height(){
        int k=0;
        for(int i=1;i<=n;++i) rk[sa[i]]=i;
        for(int i=1;i<=n;++i){
            if(rk[i]==1) continue;
            //height[1]=0
            if(k) --k;
            //因为h[i]>=h[i-1]-1
            int j=sa[rk[i]-1];
            while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k]) ++k;
            height[rk[i]]=k;
            //h[i]=height[rk[i]];
        }
    }
    
    int main(){
        scanf("%s",s+1);
        n=strlen(s+1);
        m=122;
        //ascii('z')=122,表示字符个数
        //第一次读入字符直接按照ascii码来
        get_SA();
        //get_height();
        return 0;
    }
  • 相关阅读:
    值类型和引用类型
    0513二分查找练习
    0512随机4位验证码
    0511java 随机6个不同的彩票数
    随机数的产生机制
    0510Java 练习
    0509java练习题
    java循环作业
    字符集的由来及发展
    hdu2577_键入字母
  • 原文地址:https://www.cnblogs.com/FrankChen831X/p/12374187.html
Copyright © 2020-2023  润新知