洛谷P3809 【模板】后缀排序
算法使用方向
解决每一段后缀数组的字典序排序
比如ababa
排完序后就是
5 a
3 aba
1 ababa
4 ba
2 baba
算法核心
一种类似于倍增的思路可以将
O
(
n
2
)
O(n^2)
O(n2)倍的复杂度降为
O
(
n
l
o
g
(
n
)
)
O(nlog(n))
O(nlog(n))
大概就是第一次比较每个串第一位,相当于排一次序(桶排)
这时你排序第二次,
由于是同一个串就会有一个性质:
你其实在排第一次序的时候已经得出了每个串第二位的大小顺序,只需要下标-1即可。
这样你就可以成倍的比较字符串的大小了。
算法难点
数组的定义很容易乱(我在代码中有详细定义解释)
所以你要记住比如:
这个数组对应总顺序(无重复),
那个数组对应关于第一关键字大小(可以重复,因为对应的是桶排序用的桶)
代码+详解
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<iostream>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
#define M 2000005
int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){
if(c=='-')
f=-f;
c=getchar();
}
while(c>='0'&&c<='9'){
x=(x<<3)+(x<<1)+(c^48);
c=getchar();
}
return x*f;
}
void write(int x){
printf("%d",x);
return ;
}
int x[M],y[M],c[M],sa[M];
//x[i]关于第一关键字(下标是原串的原位置,值是该串对于第一个关键字属于第几个桶)
//y[i]关于第二关键字(下标是对于第二关键字排名第几,值是(排名为下标)的串的原位置)
//c[i]是用来计算桶顶的串对于两个关键字总的来说的排名(下标是第几个桶,值是桶顶的串的总排名)
//sa[i]是用来映射对于总排名的原坐标的(下标是总排名,值是原位置)
char s[M];
int main(){
scanf("%s",s+1);
int n=strlen(s+1);
int m=122;
for(int i=1;i<=n;i++)
c[x[i]=s[i]]++;
for(int i=1;i<=m;i++)
c[i]+=c[i-1];
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-k+1;i<=n;i++)
y[++num]=i;
for(int i=1;i<=n;i++)
if(sa[i]>k)
y[++num]=sa[i]-k;
//第二关键字的排名通过前一次的总排名拿出来
for(int i=1;i<=m;i++)
c[i]=0;
for(int i=1;i<=n;i++)
c[x[i]]++;
for(int i=1;i<=m;i++)
c[i]+=c[i-1];
for(int i=n;i>=1;i--)
sa[c[x[y[i]]]--]=y[i],y[i]=0;
//取出第二个关键最小的,他就在他对应的第一个关键字桶的最后位
swap(x,y);
//重构x[i],这里的y[i]就是原来的x[i]
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;
m=num;
}
for(int i=1;i<=n;i++)
write(sa[i]),putchar(' ');
}
t h a n k s thanks thanks f o r for for w a t c h i n g watching watching