用倍增法构造后缀数组中的SA及RANK数组
感觉后缀数组很难学的说= = 不过总算是啃下来了
首先 我们需要理解一下倍增法构造的原理
设原串的长度为n 对于每个子串 我们将它用'\0'补成长度为2^k的串(2^k-1<n<=2^k)
比如串aba的子串就有 aba'\0' ba'\0''\0' a'\0''\0''\0'
每次操作我们可以排出所有长度为 2^x的子串的大小
比如串aba的排序过程
第一遍 a a b
第二遍 a'\0' ab ba
第三遍 a'\0''\0''\0' aba'\0' ba'\0''\0'
理解这些后 我们可以先写一个 nlog^2n的快排实现的方法
这种方法比较好写 如果n<=10^5就放心地去用吧
//SA nlog^2n #include <cstdio> #include <cstring> #include <cmath> #include <algorithm> #define rep(i,n) for(int i=1;i<=n;++i) #define imax (x>y?x:y) #define imax (x<y?x:y) using namespace std; const int N=100010; struct node { int x,y,ma; }tr[N]; char ch[N]; int r[N<<1],sa[N]; int n; bool cmp(node aa,node bb) { return aa.x<bb.x||(aa.x==bb.x&&aa.y<bb.y); } void getsa() { for(int i=1;1<<(i-1)<n;++i) { rep(j,n) { tr[j].x=r[j]; tr[j].y=r[j+(1<<i-1)]; tr[j].ma=j; } sort(tr+1,tr+1+n,cmp); int cnt=0; rep(j,n) r[tr[j].ma]=tr[j].x==tr[j-1].x&&tr[j].y==tr[j-1].y?cnt:++cnt; } rep(j,n) sa[r[j]]=j; } int main() { scanf("%s",ch+1); n=strlen(ch+1); rep(i,n) r[i]=ch[i]; getsa(); printf("RANK: "); rep(i,n) printf("%d ",r[i]); printf("\nSA: "); rep(i,n) printf("%d ",sa[i]); return 0; }
然而 考虑到rank数组的特殊性(一定<=n) 我们还可以使用基数排序把复杂度降到nlogn
这样就可以解决n<=10^6的问题啦
然而这个的确比较容易写错 并且需要先掌握基数排序的原理
基数排序从直观上是需要链表去做的 然而只用一个数组也同样可以很方便的实现
具体可以参考下代码
//SA nlogn(n=1需要特判下 这里懒得写了) #include <cstdio> #include <cstring> #include <cmath> #include <algorithm> #define rep(i,n) for(int i=1;i<=n;++i) #define imax (x>y?x:y) #define imax (x<y?x:y) using namespace std; const int N=1000010,S=128;//通常字符都是在0-127之间的 char ch[N]; int sum[N],r[2][N<<1],sa[2][N]; int n,t; void getsa(int i) //这里面的SA值并非最后的SA值 但保证对应rank值相等的一定相邻 从而方便比较大小 { memset(sum,0,sizeof(sum)); rep(j,n) ++sum[r[t][j+i]]; rep(j,n) sum[j]+=sum[j-1]; rep(j,n) sa[0][sum[r[t][j+i]]--]=j; memset(sum,0,sizeof(sum)); rep(j,n) ++sum[r[t][j]]; rep(j,n) sum[j]+=sum[j-1]; for(int j=n;j;--j) //基数排序从第二次排序开始是一定要倒序找的(如果不懂的话自行搜索下基数排序) sa[1][sum[r[t][sa[0][j]]]--]=sa[0][j]; } int main() { scanf("%s",ch+1); n=strlen(ch+1); rep(i,n) sum[ch[i]]=1; for(int i=1;i<S;++i) sum[i]+=sum[i-1]; rep(i,n) r[0][i]=sum[ch[i]];//函数外的sum用于求出初始排名 for(int i=1;i<n;i<<=1) { getsa(i); t^=1; rep(j,n) r[t][sa[1][j]]=r[t^1][sa[1][j]]==r[t^1][sa[1][j-1]]&& r[t^1][sa[1][j]+i]==r[t^1][sa[1][j-1]+i]? r[t][sa[1][j-1]]:r[t][sa[1][j-1]]+1; if(r[t][sa[1][n]]==n)break;//已经排好序了便可以提前退出 } printf("RANK: "); rep(i,n) printf("%d ",r[t][i]); printf("\nSA: "); rep(i,n) printf("%d ",sa[1][i]); return 0; }