洛谷P3809 【模板】后缀排序 后缀数组

网址:https://www.luogu.org/problem/P3809

题意:

把一个字符串的所有后缀按照字典序排序。字符串长度小于$1e6$。

题解:

裸的后缀数组,原理就是先把起始位置是$i$后缀作为第一关键字,$i+1$的作为第二关键字,然后合并关键字,得到新的第一关键字,然后$i+2$作为第二关键字,再合并,然后依次倍增。具体实现过程见代码注释:

参考博客:

https://www.cnblogs.com/victorique/p/8480093.html

https://www.luogu.org/blog/black-jokers/solution1-p3809

https://www.cnblogs.com/ezoiLZH/p/9607849.html

AC代码:

#include <bits/stdc++.h>
using namespace std;
const int MAXN=1000005;
struct SuffixArray
{
    int n,m;
    int tax[MAXN],rank[MAXN],tp[MAXN],sa[MAXN];
    //sa[i]=j,表示第i名的后缀是从j开始的(注意存的是下标)
    //rank[i]=j,从i开始的后缀是第j名的(和sa互逆,是排名(值))
    //tp[i],第二关键字为 i 的后缀是从j开始的 (相当于是第二关键字的sa数组,存的是下标)
    //tax[i]=j,表示第一关键字为i的数有j个(基数排序时用的桶,是值)
    char ch[MAXN];
    void sort()//基数排序
    {
        for(int i=0;i<=m;++i)//桶清零
            tax[i]=0;
        for(int i=1;i<=n;++i)//每个数放到对应的桶,得到的是从i开始的后缀的第一关键字是第rank[i]名,
        //这样的名次的后缀有tax[rank[i]]个
            ++tax[rank[i]];
        for(int i=1;i<=m;++i)//加上前面的桶,排序完成,得到排名
            tax[i]+=tax[i-1];
        for(int i=n;i>=1;--i)//以i为第二关键字的后缀是后缀tp[i],tax[rank[tp[i]]]=j是以i为第二关键
        //字的后缀的第一关键字为第一关键字的后缀有j个,由于基数排序后求前缀和已经知道其排名,所以就把这
        //个排名对应的那个后缀tp[i]记录下来,同时排名减1。每一次都是取以i为第二关键字的后缀的第一关键字
        //为第一关键字的后缀的最后一名,这样子就相当于是第二关键字相同,按照第一关键字排序了,如果第二关
        //键字不相同,也是第一关键字大的,一定越靠后,此时保证了按照第一第二关键字进行排序。
            sa[tax[rank[tp[i]]]--]=tp[i];
    }
    bool cmp(int a,int b,int k)
    {
        //现在这个tp是上一次rank,
        //前项的意思是从sa[i]开始和从sa[i-1]开始的后缀是的名次是否一样,
        //后项同理,加j之后意味着比较的名次是第二关键字。保证不会越界
        return tp[a]==tp[b]&&tp[a+k]==tp[b+k];
    }
    void get_sa()
    {
        n=strlen(ch+1);
        for(int i=1;i<=n;++i)
            m=max(m,rank[i]=ch[i]-'0'),tp[i]=i;//第一轮第一关键字直接按ASCII码,第二关键字按序号
        sort();//第一轮基数排序
        for(int p=0,j=1;p<n;j<<=1,m=p)
        {
            p=0;//清空
            for(int i=1;i<=j;++i)
                tp[++p]=n-j+i;//后面的这些后缀i小于等于j的,只有它自己了,所以排在最后面,第二关键字按长度减小而增大
            for(int i=1;i<=n;++i)
                if(sa[i]>j)
                    tp[++p]=sa[i]-j;//sa[i]>j证明后缀i的长度大于j,tp[sa[i]-j]就是后缀sa[i]-j的第二关键字,此时仍是乱序
            sort();//第二轮及以后的基数排序
            swap(rank,tp);//tp没有用了,我们将rank的信息复制给tp。同时把rank空出来
            rank[sa[1]]=p=1;//空出来的rank记录新的sa下的对应关系,进行下一轮排序
            for(int i=2;i<=n;++i)
                rank[sa[i]]=cmp(sa[i],sa[i-1],j)?p:++p;
        }
    }
};
SuffixArray sa;
int main()
{
    //scanf("%d",&sa.n);
    scanf("%s",sa.ch+1);
    sa.get_sa();
    for(int i=1;i<=sa.n;++i)
        printf("%d%c",sa.sa[i],(i==sa.n?'\n':' '));
    return 0;
}

  

posted @ 2019-08-16 01:30  Aya_Uchida  阅读(196)  评论(0编辑  收藏  举报