(文章更新中)
作为一个小菜鸟,也开始学后缀数组了。
关于模板哪些事
后缀数组第一步:后缀排序—求sa和rk
在后缀数组中,有一环避不开的就是后缀排序了,后缀排序中,我们使用的是基数排序(不懂的可以手动跳转以下–>基数排序(百度百科))这里运用的也是基数排序,这里先推荐一道后缀排序的板子:P3809后缀排序
同时,还需要倍增,把复杂度优化到nlogn。
其实还有一种O(n)的算法,但代码复杂度太高,所以使用不多。
关于后缀排序的板子和各种讲解,网上也很多,这里就不单独拉出来了,就简单讲一下自己的体会:
- 在后缀排序以及后面的后缀数组中,由于涉及的桶和for循环比较多,所以一定要注意细节,小心不要打错,如果数组名比较相似,改都很难改好。
- 对于其中的m,初始值一定要设好,不要太吝啬了,之前一道题目就因为初始值太小,导致后面的一个数组爆了,RE只有40分。
代码如下:
void qsort() {
for(int i=0; i<=m; ++i)
c[i]=0;
for(int i=1; i<=n; ++i)
++c[x[i]];
for(int i=1; i<=m; ++i)
c[i]+=c[i-1];
for(int i=n; i>=1; --i)
sa[c[x[y[i]]]--]=y[i];
}
void get_sa() {
for(int i=1; i<=n; ++i)
x[i]=s[i],y[i]=i;
qsort();
for(int k=1,num; num!=n; k<<=1,m=num) {
num=0;
for(int i=n-k+1; i<=n; ++i)
y[++num]=i;
for(int i=1; i<=n; ++i)
if(sa[i]>k)
y[++num]=sa[i]-k;
qsort();
swap(x,y),num=0;
for(int i=1; i<=n; ++i)
x[sa[i]]=(y[sa[i]]==y[sa[i-1]])&&(y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
}
for(int i=1; i<=n; ++i)
rk[sa[i]]=i;
}
后缀数组第二步—求height数组
在后缀数组中,height数组就是在排好序之后相邻的两个字符串的lcp。
求法:
- O(n2) 暴力枚举
暴力枚举想必大家都会,暴力匹配,不过这样的效率太低,速度很慢。 - O(nlogn)二分求lcp
于是考虑优化,由于lcp本身可以用哈希+二分优化到logn的复杂度,所以这里自然可以优化成nlogn。 - O(n)运用一个神奇的性质求
height数组还有一个神奇的性质:height[rk[i]]>=height[rk[i-1]]-1。运用这个性质,我们就可以线性求出height数组了
下面就是一波模板:
int n,m,sa[N],h[N],rk[N],s[N],a[N],x[N],y[N],c[N];
struct Sa {
void qsort() {
for(int i=0; i<=m; ++i)
c[i]=0;
for(int i=1; i<=n; ++i)
++c[x[i]];
for(int i=1; i<=m; ++i)
c[i]+=c[i-1];
for(int i=n; i>=1; --i)
sa[c[x[y[i]]]--]=y[i];
}
void get_sa() {
for(int i=1; i<=n; ++i)
x[i]=s[i],y[i]=i;
qsort();
for(int k=1,num; num!=n; k<<=1,m=num) {
num=0;
for(int i=n-k+1; i<=n; ++i)
y[++num]=i;
for(int i=1; i<=n; ++i)
if(sa[i]>k)
y[++num]=sa[i]-k;
qsort();
swap(x,y),num=0;
for(int i=1; i<=n; ++i)
x[sa[i]]=(y[sa[i]]==y[sa[i-1]])&&(y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
}
for(int i=1; i<=n; ++i)
rk[sa[i]]=i;
}
void get_head() {
int k=0,j;
for(int i=1; i<=n; ++i) {
if(rk[i]==1) continue;
if(k) --k;
j=sa[rk[i]-1];
while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k])
++k;
h[rk[i]]=k;
}
}
} SA;