西安回来后一个月没写代码了,前段时间太忙,这两天算是好点了,学习了一下后缀数组。
这里有几个关键的数组。
s是原始字符数组,sa是后缀数组,sa[i]表示第i大后缀的开始位置,rank是名次数组,rank[i]表示位置i的数组是第几大(相当于sa[i]反过来),height[i]是后缀sa[i]和后缀sa[i-1]的最长公共前缀,x是当前rank,y是辅助数组。
主要思路就是这个图,每次给后缀的前2^k个字符排序,最多logn次排序,如果用基数排序复杂度为O(n),总复杂度为O(nlogn)。
关于height数组的计算:设h[i]=height[rank[i]],h[i]>=h[i-1]-1(因为后缀i比后缀i-1少最左边一个字符,因此h[i]最小都是h[i-1]-1),这样不必每次从头比较了,复杂度O(n)。
有一点要注意的是,一般在s数组最后加一个0,并且其它元素都非0。这是为了防止数组越界,省去了特判的麻烦(因为如果s为1111,sa={0,1,2,3},k=1,计算x的时候比较最后两个元素第一维的时候相等,此时比较第二维,最后一个位置+1就越界了,因为最后一个位置是没有第二维的),加了个0后,如果第一维相等,也就是y[sa[i-1]]==y[sa[i]],这个时候sa[i-1]+k和sa[i]+k是小于n的,因为如果第一维相等,两个串最后一个字符肯定不会是0,否则就不会相等(因为只有最后一个字符是0),所以加了k也不会越界。
struct SuffixArray{ int s[MAXN]; //原始字符数组 int sa[MAXN]; //后缀数组,sa[i]为第i小后缀在s中的下标,最后一个字符是0,前面非0 int rank[MAXN]; //名次数组,rank[i]为s[i]后缀是第几小,rank[n-1]=0 int height[MAXN]; //height[i]为sa[i-1]和sa[i]的最长公共前缀 int c[MAXN]; //基数排序数组 int t[MAXN],t2[MAXN]; //x,y辅助数组 int n; //字符个数 void clear(){ n=0; memset(sa,0,sizeof(sa)); } //m为最大字符值+1,调用前需设置好s和n void build_sa(int m){ int i,*x=t,*y=t2; //基数排序 for(i=0;i<m;i++) c[i]=0; for(i=0;i<n;i++) c[x[i]=s[i]]++; for(i=1;i<m;i++) c[i]+=c[i-1]; for(i=n-1;i>=0;i--) sa[--c[x[i]]]=i; for(int k=1;k<=n;k<<=1){ int p=0; //用sa数组排序第二关键字 for(i=n-k;i<n;i++) y[p++]=i; for(i=0;i<n;i++) if(sa[i]>=k) y[p++]=sa[i]-k; //基数排序第一关键字 for(int i=0;i<m;i++) c[i]=0; for(int i=0;i<n;i++) c[x[y[i]]]++; for(int i=1;i<m;i++) c[i]+=c[i-1]; for(int i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i]; swap(x,y); p=1; x[sa[0]]=0; for(int i=1;i<n;i++) x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++; if(p>=n) break; m=p; } } void build_height(){ int k=0; for(int i=0;i<n;i++) rank[sa[i]]=i; height[0]=0; for(int i=0;i<n-1;i++){ if(k) k--; int j=sa[rank[i]-1]; while(s[i+k]==s[j+k]) k++; height[rank[i]]=k; } } };