前言
复习小记差不多是写给自己看的,步骤都比较简略,主要总结要点。初学者的话还是去看罗穗骞的论文吧。
这个东西我初二就会了23333333
DA 算法构造
DA
就是倍增算法。
每一层都有长度相等的一二关键字。
SA
就是排名第
i
的后缀的开始位置,
数组
Wv
就是第二关键字第
i
的后缀的第一关键字(
求
SA
时倒着做来维护第二关键字的有序。
最后来处理这一轮的
x
,因为有些后缀在当前排序长度下会完全一样,所以用类似离散化的方法,比较函数bool cmp(int *r,int i,int j,int l){return r[i]==r[j]&&r[i+l]==r[j+l];}
为了不特殊判断边界,我们只需将
r
(关键字数组,这里我们将r[i]==r[j]
,那至少
这里有个小优化,当离散化后发现所有后缀都互不相同时,我们可以直接退出循环。这个优化实测效果好到飞起。
然后我们就扫一遍求
rank
即可。
这里是
DA
算法的代码(短小精悍):
void DA()
{
int mx=0;
for (int i=0;i<n;i++)mx=max(x[i]=Wv[i+1]=s[i]-'a'+1,mx);
for (int i=0;i<=mx;i++)Ws[i]=0;
for (int i=1;i<=n;i++)Ws[Wv[i]]++;
for (int i=1;i<=mx;i++)Ws[i]+=Ws[i-1];
for (int i=n;i>=1;i--)SA[Ws[Wv[i]]--]=i-1;
/*初始处理这一段,如果字符集很大,就换用快排*/
int l=1,p;
while (l<=n)
{
p=0;
for (int i=n-l;i<n;i++)y[++p]=i;
for (int i=1;i<=n;i++)if(SA[i]>=l)y[++p]=SA[i]-l;
mx=0;
for (int i=1;i<=n;i++)mx=max(Wv[i]=x[y[i]],mx);
for (int i=0;i<=mx;i++)Ws[i]=0;
for (int i=1;i<=n;i++)Ws[Wv[i]]++;
for (int i=1;i<=mx;i++)Ws[i]+=Ws[i-1];
for (int i=n;i>=1;i--)SA[Ws[Wv[i]]--]=y[i];
for (int i=0;i<=n;i++)y[i]=x[i],x[i]=0;
p=1;
x[SA[1]]=1;
for (int i=2;i<=n;i++)x[SA[i]]=cmp(SA[i-1],SA[i],l)?p:++p;
if (p==n)break;//小优化
l<<=1;
}
for (int i=1;i<=n;i++)rank[SA[i]]=i;
}
至于 DC3 ,我不会,一般出题人不会丧心病狂卡你的构造,如果实在不行,那估计正解就是 SAM (后缀自动机)了。
求 height 数组
我们定义
height
表示
length(LCP(suffix(SA[i]),suffix(SA[i−1])))
。
然后令
rank[i]≤rank[j]
,那么
length(LCP(suffix(i),suffix(j)))
显然为
然后怎么线性求 height 呢?
我们令 h[i]=height[rank[i]] ,可证 h[i]≥h[i−1]−1 (证明见论文)。
然后我们就可以按照 h[1] , h[2] …… h[n] 的顺序计算。这样时间复杂度显然为 O(n) 。
求 height 的代码如下:
void get_height()
{
int k=0;
for (int i=0;i<n;i++)
{
k=k?k-1:0;
if (rank[i]==1)
continue;
int j=SA[rank[i]-1];
while (i+k<n&&j+k<n&&s[i+k]==s[j+k])
k++;
height[rank[i]]=k;
}
}
常见思路
首先肯定要搞出以上全部东西,然后按排序后的顺序来分析。
通常是单调栈把
height
数组一顿乱搞。
当然还有按照排序后的顺序数据结构维护一些神奇的东西,比如搞棵主席树处理区间问题。
据说
SA
数组搞出来后可以快速建出后缀树,反正我还不会,会了之后再更吧。