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

题目描述

读入一个长度为 n n 的由大小写英文字母或数字组成的字符串,请把这个字符串的所有非空后缀按字典序从小到大排序,然后按顺序输出后缀的第一个字符在原串中的位置。位置编号为 1 1 到 n n 。

输入输出格式

输入格式:
一行一个长度为 n n 的仅包含大小写英文字母或数字的字符串。

输出格式:
一行,共n个整数,表示答案。

输入输出样例

输入样例#1:
ababa
输出样例#1:
5 3 1 4 2
说明

n <= 10^6

分析:后缀数组板题。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>

const int maxn=1e6+7;

using namespace std;

char s[maxn];
int len;
int x[maxn],y[maxn],sa[maxn],c[maxn];

//c是一个桶,sa是后缀数组,sa[i]表示排名第i的是第几个串
//x表示rank,x[i]表示第i个串的排名,也是第一关键字
//y[i]表示第二关键字排名第i的是第几个串

void getsa()
{
    int m=1000;
    for (int i=0;i<=m;i++) c[i]=0;//清空桶
    for (int i=0;i<len;i++) x[i]=s[i];//一开始的rank为第一个字符的大小,可以用ascii码表示
    for (int i=0;i<len;i++) c[x[i]]++;//加入桶
    for (int i=1;i<=m;i++) c[i]=c[i]+c[i-1];//前缀和,c[i]可以表示前面有多少个比他大
    for (int i=len-1;i>=0;i--) sa[--c[x[i]]]=i; //更新sa
    for (int k=1;k<=len;k<<=1)//通过第一位依次倍增出每一位
    {
        int num=0;
        for (int i=len-k;i<len;i++) y[num++]=i;//第n-k到第n个串没有后面的后缀,排在最前面
        for (int i=0;i<len;i++) if (sa[i]>=k) y[num++]=sa[i]-k;//加入排名第i的是第j个串,此时这个串就是第j-k个串的右半部分,也就是第二关键字的排名直接可以通过sa获得
        for (int i=0;i<=m;i++) c[i]=0;
        for (int i=0;i<len;i++) c[x[i]]++;
        for (int i=1;i<=m;i++) 
            c[i]=c[i]+c[i-1];           
        for (int i=len-1;i>=0;i--) 
            sa[--c[x[y[i]]]]=y[i],y[i]=0;
        //通过先把第二关键字大的先搞出来,可以实现双关键字排序
        swap(x,y);
        num=1;
        x[sa[0]]=1;//通过sa更新x,其中如果两个串完全相同,那么rank,也就是x也相同
        for (int i=1;i<len;i++)
        {
            if ((y[sa[i]]!=y[sa[i-1]]) || (y[sa[i]+k]!=y[sa[i-1]+k]))
            {
                x[sa[i]]=++num;
            }
            else x[sa[i]]=num;
        }
        if (num>=len) break;
        m=num;
    }
}

int main()
{
    scanf("%s",s);
    len=strlen(s);  
    getsa();
    for (int i=0;i<len;i++) printf("%d ",sa[i]+1);//答案就是sa数组,因为下标问题所以要加1
}

猜你喜欢

转载自blog.csdn.net/liangzihao1/article/details/80340705