题目描述
读入一个长度为 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
}