题目
July 8, 2021 · View on GitHub
后缀数组 (SA) 是一种重要的数据结构,通常使用倍增或者 DC3 算法实现,这超出了我们的讨论范围。
在本题中,我们希望使用快排、Hash 与二分实现一个简单的 O(nlog2n) 的后缀数组求法。
详细地说,给定一个长度为 n 的字符串 S(下标 0∼n−1),我们可以用整数 k(0≤k<n) 表示字符串 S 的后缀 S(k∼n−1)。
把字符串 S 的所有后缀按照字典序排列,排名为 i 的后缀记为 SA[i]。
额外地,我们考虑排名为 i 的后缀与排名为 i−1 的后缀,把二者的最长公共前缀的长度记为 Height[i]。
我们的任务就是求出 SA 与 Height 这两个数组。
输入格式
输入一个字符串,其长度不超过 30 万。
字符串由小写字母构成。
输出格式
第一行为数组 SA,相邻两个整数用 1 个空格隔开。
第二行为数组 Height,相邻两个整数用 1 个空格隔开,我们规定 Height[1]=0。
输入样例:
ponoiiipoi
输出样例:
9 4 5 6 2 8 3 1 7 0
0 1 2 1 0 0 2 1 0 2
参考答案
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int x[N],y[N],c[N],sa[N],rk[N],height[N],wei[30],n,m;
char s[N];
void SA()
{
for ( int i=1; i<=n; i++ )
++c[x[i]=s[i]]; //c是桶,x[i]是第i个元素的第一关键字
for ( int i=2; i<=m; i++ )
c[i]+=c[i-1]; //做c的前缀和,我们就可以得出每个关键字最多是在第几名
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; //y[i]表示第二关键字排名i的数,第一关键字的位置
//第n-k+1到第n位是没有第二关键字的 所以排名在最前面
for ( int i=1; i<=n; i++ )
if ( sa[i]>k ) y[++num]=sa[i]-k; //排名为i的数 在数组中是否在第k位以后
//如果(sa[i]>k) 那么可作为别人的2nd关键字,就把它1st关键字的位置添加进y,i枚举第二关键字的排名,靠前的先入队
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]; //第一关键字排名为1~i的数个数
for ( int i=n; i>=1; i-- )
sa[c[x[y[i]]]--]=y[i],y[i]=0;
//因为y的顺序是按照第二关键字的顺序来排的
//第二关键字靠后的,在同一个第一关键字桶中排名越靠后
//基数排序
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;
//因为sa[i]已经排好序了,所以可以按排名枚举,生成下一次的第一关键字
if ( num==n ) break;
m=num;
}
for ( int i=1; i<=n; i++ )
printf( "%d ",sa[i]-1 );
}
void get_height()
{
int k=0;
for ( int i=1; i<=n; i++ )
rk[sa[i]]=i;
for ( int i=1; i<=n; i++ )
{
if ( rk[i]==1 ) continue;//第一名height为0
if ( k ) --k; //h[i]>=h[i-1]-1;
int j=sa[rk[i]-1];
while ( j+k<=n && i+k<=n && s[i+k]==s[j+k] ) ++k;
height[rk[i]]=k; //h[i]=height[rk[i]];
}
putchar(10);
for ( int i=1; i<=n; i++ )
printf( "%d ",height[i] );
}
int main()
{
scanf( "%s",s+1 );
// gets( s+1 );
n=strlen( s+1 ); m=122; //n表示原字符串长度,m表示字符个数,ascll('z')=122
SA();
get_height();
}