后缀数组
后缀数组
定义
后缀是什么就不用说了吧…
在后缀数组中,我们把从第i
个下标开始的后缀称为第i
个后缀(我个人比较习惯字符串下标从1
开始)
这样一来我们得到n
个后缀
通过倍增的思想,我们可以再
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)的时间内对这n
个后缀按照字典序排序
变量名:
sa[i]
:表示排名第i
的后缀是第几个后缀rk[i]
表示第i
个后缀的排名是多少height[i]
表示sa[i]
与sa[i-1]
的最长公共前缀(LCP)
举个例子:
字符串aababb
的后缀分别是:
aababb
ababb
babb
abb
bb
b
第一次我们以这些后缀的第一个字符作为关键字排序
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-57UGFAE3-1614075926050)(C:\Users\Typedef\Pictures\笔记\屏幕截图 2021-02-21 082349.png)]
排完结果是:
aababb
ababb
abb
babb
bb
b
通过基数排序我们可以在 O ( n ) O(n) O(n)时间内排好序
每次我们考虑的字符是倍增的
若我们已经按前k
个字符排好序
那么我们以前k
个字符作为第一关键字,k~2k
个字符为第二关键字进行基数排序
这样我们就以前2k
个字符排好序了
具体来说,每次排好序后,我们把排序的前k
个字符进行离散化,这样以离散化的结果为第一关键字进行下一轮
l o g ( n ) log(n) log(n)轮之后就求出来了
实现细节:
:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zXCEZ0ku-1614075926052)(C:\Users\Typedef\Pictures\笔记\基数.png)]
基数排序过程中,我们用桶存下每个数出现的次数
然后对桶过一遍前缀和,得到一个类似每个数的排名的一个数组
为保障排序的稳定性,我们在原数组中逆序更新排名,更新排名的同时还要更新前缀和数组
最终在 O ( n ) O(n) O(n)的时间内得到每个数的排名
那么如何进行双关键字的排序呢?
我们首先按照第二关键字进行基数排序
然后再按照第一关键字对排序结果进行排序
就OK
了
那么我们怎么得到第二关键字呢?
观察发现.第i
个后缀的第二关键字其实就是第i+k
个后缀的第一关键字
这样我们可以顺序求出第二关键字,注意考虑边界
height
数组怎么求呢?
补充几个LCP
的性质(i,j,k
按照字典序排序,且rk[i]<rk[k]<rk[j]
)
- l c p ( i , j ) = l c p ( j , i ) lcp(i,j)=lcp(j,i) lcp(i,j)=lcp(j,i)
- l c p ( i , i ) = l e n ( i ) lcp(i,i)=len(i) lcp(i,i)=len(i)
- l c p ( i , k ) = l c p ( k , j ) lcp(i,k)=lcp(k,j) lcp(i,k)=lcp(k,j)
/*************************************************************************
> File Name: p3809[模板]后缀排序.cpp
> Author: typedef
> Mail: 1815979752@qq.com
> Created Time: 2021/2/23 7:43:45
************************************************************************/
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+7;
int n,m;
char s[N];
int sa[N],x[N],y[N],c[N],rk[N],height[N];//x是第一关键字,y是第二关键字,c是关键字的个数
void get_sa(){
for(int i=1;i<=n;i++) c[x[i]=s[i]]++;
for(int i=2;i<=m;i++) c[i]+=c[i-1];
for(int i=n;i;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=2;i<=m;i++) c[i]+=c[i-1];
for(int i=n;i;i--) sa[c[x[y[i]]]--]=y[i],y[i]=0;
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;
if(num==n) break;
m=num;
}
return;
}
void get_height(){
for(int i=1;i<=n;i++) rk[sa[i]]=i;
for(int i=1,k=0;i<=n;i++){
if(rk[i]==1) continue;
if(k) k--;
int j=sa[rk[i]-1];
while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k]) k++;
height[rk[i]]=k;
}
}
int main(){
scanf("%s",s+1);
n=strlen(s+1),m=122;
get_sa();
// get_height();
for(int i=1;i<=n;i++) printf("%d ",sa[i]);
puts("");
// for(int i=1;i<=n;i++) printf("%d ",height[i]);
// puts("");
return 0;
}