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 ",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; }