妈呀被后缀数组虐的死去活来。。。一开始去看后缀树。。好像很麻烦的样子,后来再看后缀数组,理论好像很好懂的样子,不会实现呀。。
这充分证明了我有多傻逼,花了整整一天才看懂如何实现,还是因为我基数排序没学好?。。。
(update:然后又花了一天看懂求height。。。)
好好看这张图。。。很重要。。
无脑直接排序的时间复杂度是O(n^2logn)因为字串比较是O(n)
这里介绍用倍增求后缀数组的方法。。
第i次排序将每个字符向后的2^i组成的字符串排序(看上面的图意会一下),无脑排序O(nlogn),但是可以利用后缀间的性质O(n)搞基数排序。
第一次对单个字符排序的时候单独拉出来处理,直接桶排就行了。。从第二次开始,我们要以上一次排序的rank作为第一关键字,再把补上的后一半的字符串上一次的rank作为第二关键字进行排序(看图脑补),这时候开始真正的基数排序。。先维护一个第二关键字递增的序列,后面的没有第二关键字的就视为0,这个可以利用上一次的sa数组弄到(看代码脑补)。。之后把第一关键字一一扔进桶里,然后最关键的地方,按第二关键字递减的顺序把桶里面的东西取出,因为第一关键字一样第二关键字大的更大嘛。。这时候新的sa就搞定了。。然后要维护新的rank,按照离散化的思路顺序处理sa数组即可(看代码脑补)。。
为了发挥后缀数组的威力,我们还要求出height数组,height[i]表示排名第i的后缀和排名的i-1的后缀的最长公共前缀,height[1]=0,然后有一个性质,height[rank[i]]>=height[rank[i]-1]-1,这个看论文证明。这样就可以从1-n顺序的求出height[rank[i]],查询任意两后缀的最长公共前缀就是求min(height[j])rank[j]在这两个后缀的rank直接,就可以rmq做了。。
由于这个貌似有太多看了代码才理解的了的地方,我只能讲成这样了TAT。。
#include<cstdio> #include<iostream> #include<cstring> #define N 200005 using namespace std; int i,j,n,rank[N],sa[N],r2[N],buc[N],sec[N],h[N]; char s[N]; bool cmp(int x,int y,int d){return r2[x]==r2[y]&&r2[x+d]==r2[y+d];} void getsa() { int i,p=0,d=1; for (i=0;i<=n;i++) buc[i]=0; for (i=0;i<n;i++) buc[rank[i]=(int)s[i]]++; for (i=1;i<=256;i++) buc[i]+=buc[i-1]; for (i=n-1;i>=0;i--) sa[--buc[rank[i]]]=i; while (p<n) { for (i=0;i<n;i++) r2[i]=rank[i],buc[i+1]=0; for (p=0,i=n-d;i<n;i++) sec[p++]=i; for (i=0;i<n;i++) if (sa[i]>=d) sec[p++]=sa[i]-d; for (i=0;i<n;i++) buc[rank[sec[i]]]++; for (i=1;i<=n;i++) buc[i]+=buc[i-1]; for (i=n-1;i>=0;i--) sa[--buc[rank[sec[i]]]]=sec[i]; for (rank[sa[0]]=1,i=1,p=1;i<n;i++) rank[sa[i]]=cmp(sa[i],sa[i-1],d)? p:++p; d*=2; } } void geth() { int i,j,k; for (i=0,k=0;i<n;i++) if (rank[i]==1) h[1]=0; else { j=sa[rank[i]-2]; while (s[j+k]==s[i+k]) k++; h[rank[i]]=k;if (k) k--; } } int main() { scanf("%s",s); n=strlen(s); for (i=n;i<2*n;i++) s[i]='$'; getsa(); if (n==1) rank[0]=1; geth(); for (i=0;i<n;i++) printf("%d ",sa[i]+1);printf("\n"); for (i=2;i<=n;i++) printf("%d ",h[i]); }
原文地址:http://blog.csdn.net/tag_king/article/details/45190183