后缀排序
Tim正在自学《数据结构》,他刚刚学会如何比较两个字符串大小。书上是这么说的(和Pascal语言中的比较规则相同,学习过Pascal语言的同学可以跳过这段):
比较两个不同字符串s1=’p1p2p3…pN’和s2=’q1q2q3…qM’的大小,设N<=M。
若s1是s2的前缀,则s1qi,且i最小;若pis2。
Tim想通过练习熟练运用这个规则,于是打算出许多字符串,并将它们从小到大排序。可是Tim非常懒,随机写出 K个很长的字符串实在是太麻烦了。不过聪明的他想到了一个好办法,他写了一个很长的字符串,自言自语说,“我只要把这个字符串的所有后缀从小到大排序就可以了”。
Input
仅有一行,且是一个仅包含小写字母的字符串,长度K不超过10^5。
Output
有K行,每行一个数字,第i行的数字Pi表示所有后缀中,第i小的是由原字符串第Pi个字符引导的后缀。
Sample Input
mississippi
Sample Output
11
8
5
2
1
10
9
7
4
6
3
Sol:
sa[i]=j...排名第i的后缀是从原串中的第j个位置开始的
rank[i]=j...原串中第i个位置开始的后缀,排名为j
两者是个互逆的函数,整个程序就是两个数组倒来倒过
#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#define maxn 210000
#define ll long long
#define fill(a,b) memset(a,b,sizeof(a))
using namespace std;
int Rank[maxn],sa[maxn],a[maxn],wr[maxn],rsort[maxn],y[maxn];
char c[110000];
inline bool cmp(int k1,int k2,int ln)
{
return wr[k1]==wr[k2]&&wr[k1+ln]==wr[k2+ln];
}
inline void get_sa(int n,int m)
{
int k,p,ln;
memcpy(Rank,a,sizeof(Rank)); //将a数组copy给Rank
memset(rsort,0,sizeof(rsort));
for(int i=1;i<=n;i++) //对于每个字符统计它的出现次数
rsort[Rank[i]]++;
for(int i=1;i<=m;i++) //对于每个字符统计有多少个比它小
rsort[i]+=rsort[i-1];
for(int i=n;i>=1;i--)
sa[rsort[Rank[i]]--]=i;
//sa[i]=j,排在第i小的后缀串是从原字符串第j个位置开始的
ln=1; p=0;
while (p<n)
{
k=0;
for(int i=n-ln+1;i<=n;i++)
y[++k]=i;
for(int i=1;i<=n;i++)
if(sa[i]>ln) y[++k]=sa[i]-ln;
for(int i=1;i<=n;i++)
wr[i]=Rank[y[i]];
//rank[i]=j ,从i开始的后缀是第 j 名的(和 sa 互逆,是排名(值))
memset(rsort,0,sizeof(rsort));
for(int i=1;i<=n;i++)
rsort[wr[i]]++;
for(int i=1;i<=m;i++)
rsort[i]+=rsort[i-1];
for(int i=n;i>=1;i--)
sa[rsort[wr[i]]--]=y[i];
for(int i=1;i<=n;i++)
wr[i]=Rank[i];
p=1;
Rank[sa[1]]=1;
for(int i=2;i<=n;i++)
{
if(!cmp(sa[i],sa[i-1],ln)) p++;
Rank[sa[i]]=p;
}
m=p;
ln*=2;
}
for(int i=1;i<=n;i++)
printf("%d\n",sa[i]);
}
int main(){
scanf("%s",c+1);
int n=strlen(c+1);
for(int i=1;i<=n;i++)
a[i]=c[i];
get_sa(n,300);
return 0;
}

浙公网安备 33010602011771号