这篇博客不打算讲多么详细,网上关于后缀数组的blog比我讲的好多了,这一篇博客我是为本身加深印象写的。html
给大家分享了那么多,容我自私一回吧~算法
参考资料:这位dalao的blog数组
1、关于求SuffixArray的一些变量定义:spa
1. sa[i]=j,表示第i名的后缀从j开始code
**存的是下标**xml
2. rnk[i]=j,从i开始的后缀是第j名的htm
**与sa为互逆运算,存的是值**blog
3. tp[i]=j, 第二关键字为i的后缀从j开始排序
**可理解为第二关键字的SA,存的是下标**字符串
插入解释一下第一关键字和第二关键字:
咱们要对全部的后缀进行排序,怎么排呢?
开始时,咱们每一个字符的后缀存的只有它本身,因此它后缀的大小就是它的ASCII码。
咱们把每一个字符i当作(s[i],i)的二元组,若是咱们直接丢pair<int,int>里面而后std::sort,
这样的时间复杂度是O(log^2 n)的,显然不够优秀。
因此就须要用到基数排序RadixSort,不了解的自行百度。
再使用倍增法,就可使咱们排序的时间复杂度下降到O(logn)。
因此咱们要对每一个后缀的前两个字母进行排序,第一个字母的相对关系已经获得了。
第i个后缀的第二个字母,就是第i+1个后缀的第一个字母,利用这个关系咱们第二个字母的相对关系也就知道了。
咱们的tp数组就是用来记录它的,rnk[i]表示上一轮中第i个后缀的排名。
这里引用神仙attack的一句话,我以为讲的很是到位:
对于一个长度为w的后缀,你能够形象的理解为:
第一关键字针对前w2个字符造成的字符串,第二关键字针对后w2个字符造成的字符串
而后对每一个后缀的前4个字母组成的字符串排序,前8个,前16个...这就是倍增法求SA的流程了。
给出RadixSort的代码:
void RadixSort(int a[],int b[]){//基数排序 for(int i=0;i<=m;i++)tax[i]=0; for(int i=1;i<=n;i++)tax[a[i]]++; for(int i=1;i<=m;i++)tax[i]+=tax[i-1]; for(int i=n;i>=1;i--)sa[tax[a[b[i]]]--]=b[i]; }
实在不能理解RadixSort也没有关系,代码很短
再给出求SA的代码:
bool cmp(int *r,int a,int b,int k){ return r[a]==r[b]&&r[a+k]==r[b+k]; } void getSA(int a[],int b[]){ for(int i=1;i<=n;i++) m=max(m,a[i]=s[i]-'0'),b[i]=i; RadixSort(a,b); for(int p=0,j=1;p<n;j<<=1,m=p){ p=0; for(int i=1;i<=j;i++)b[++p]=n-j+i; for(int i=1;i<=n;i++)if(sa[i]>j)b[++p]=sa[i]-j; RadixSort(a,b); int *t=a;a=b;b=t; a[sa[1]]=p=1; for(int i=2;i<=n;i++) a[sa[i]]=cmp(b,sa[i],sa[i-1],j)?p:++p; } }
关于代码的解释,有时间再填坑。本蒟蒻要学的算法还不少...SA就粗略地理解一下好了
开始填坑,先补充一个东西叫height数组。
height[i]表示排名为i的后缀和排名为i-1的后缀的最长公共前缀LCP。
暴力求解时间复杂度是O(n^2),根据一个性质height[i+1]>=height[i]-1
能够O(n)时间内求出height数组,具体代码:
void getHeight(){ for(int i=1,j=0;i<=n;i++){ if(j)j--; while(s[i+j]==s[sa[rnk[i]-1]+j])j++; height[rnk[i]]=j; } }
关于这个height数组,它能够干什么,给出一张列表:
lcp(x,y)=min(heigh[x−y])lcp(x,y)=min(heigh[x−y]), 用rmq维护,O(1)查询
height数组里的最大值
首先二分答案x,对height数组进行分组,保证每一组的最小height都>=x
依次枚举每一组,记录下最大和最小长度,若sa[max]−sa[min]>=x那么能够更新答案
枚举每个后缀,第i个后缀对答案的贡献为len−sa[i]+1−height[i]