后缀数组(SA)
后缀数组
Suffix Array
时间复杂度\(O(n \log n)\)
主要涉及到三个数组:
\(sa[i]\)表示后缀数组,排名第\(i\)个的后缀编号
\(rk[i]\)表示第\(i\)个后缀的排名
\(height[i]\)表示排名为\(i\)和\(i-1\)的后缀的lcp(最长公共前缀)
易得:\(sa[rk[i]]=rk[sa[i]]=i\)
求后缀数组get_sa
倍增实现,时间复杂度\(O(n \log_2n)\)
定义:
- \(x[i]\),桶数组,里面存的是编号
- \(fu[i]\),辅助数组,里面记录上一次的sa与tong
- \(cnt[i]\),记录每一个桶中的数量
伪代码
void get_sa(){
按第一个字母排序:
1~n 编桶号,记录cnt
1~m 求cnt的前缀和
n~1 倒序枚举,保序,按第一个字母排序,sa[cnt[x[i]]--]=i;
1~n,k<<=1,倍增{
1. 按下一个关键字排序
memset,cnt
1~n fu->sa,把sa复制一遍
1~n 向右移动k位,cnt[x[fu[i]+k]]++;
1~m 求cnt前缀和
n~1 倒序枚举更新sa
2. 在按照之前排好的顺序排一次
3. 把后缀数组放入桶数组中,这样桶中就存的是前面已排序的部分的编号了
1~n fu->x
m=0;
1~n if前面的相同且这k位也相同,存入已有的桶中
else新建一个桶存入
m==n? 已经排好了
}
}
求height数组
时间复杂度\(O(n)\)
定义
\(height[i]=lcp(sa[i],sa[i-1])\)
定理
\(height[rk[i]] \ge height[rk[i-1]]-1\)
后缀1为全数组
证明:
- 当\(height[i] \le 1\)时,定理一定成立
- 当\(height[i] \gt 1\)时
\(lcp(sa[rk[i-1]],sa[rk[i-1]-1])=height[rk[i-1]] \gt 1\)
所以说后缀\(i-1\)和后缀\(sa[rk[i-1]-1]\)有着长度为\(height[rk[i-1]]\)的公共前缀
我们不妨设这个公共前缀为\(aA\),其中\(a\)为一个字符,\(A\)为一个字符串
那么后缀\(i-1\)为\(aAD\),后缀\(sa[rk[i-1]-1]\)为\(aAB(B,D都为字符串,B可能为空串)\)
所以后缀\(i\)为\(AD\),且存在后缀\((sa[rk[i-1]-1]+1)\)为\(AB\),则\(AB \lt AD\)
由于\(sa[rk[i]-1] \lt AD\)
所以\(AB \le sa[rk[i]-1] \lt AD\)
而\(AB\)和\(AD\)有公共前缀\(A\)
所以\(sa[rt[i]-1]\)和\(i\)一定有公共前缀\(A\)
所以\(height[rk[i]] \ge height[rk[i-1]]-1\)
得证
伪代码
void get_height(){
1~n 找rk
1~n 枚举后缀{
如果是第一个 continue;
k--;
找到上一个
while开始匹配
更新height
}
}
例题:P3809 【模板】后缀排序
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=2e6+10;
char s[maxn];
int n,m,x[maxn],fu[maxn],sa[maxn],cnt[maxn];
void get_sa(){
for(int i=1;i<=n;i++) cnt[x[i]=s[i]]++;
for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--) sa[cnt[x[i]]--]=i;
for(int k=1;k<=n;k<<=1){
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++) fu[i]=sa[i];
for(int i=1;i<=n;i++) cnt[x[fu[i]+k]]++;
for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--) sa[cnt[x[fu[i]+k]]--]=fu[i];
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++) fu[i]=sa[i];
for(int i=1;i<=n;i++) cnt[x[fu[i]]]++;
for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--) sa[cnt[x[fu[i]]]--]=fu[i];
for(int i=1;i<=n;i++) fu[i]=x[i];
m=0;
for(int i=1;i<=n;i++)
if(fu[sa[i]]==fu[sa[i-1]]&&fu[sa[i]+k]==fu[sa[i-1]+k]) x[sa[i]]=m;
else x[sa[i]]=++m;
if(m==n) break;
}
}
/*
int height[maxn],rk[maxn];
void get_height(){
for(int i=1;i<=n;i++) rk[sa[i]]=i;
for(int i=1,k=0;i<=n;i++){
if(rk[i]==1) continue;
if(k) k--;
int j=sa[rk[i]-1];
while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k]) k++;
height[rk[i]]=k;
}
}*/
int main(){
/*2023.5.2 hewanying P3809 【模板】后缀排序 后缀数组SA*/
scanf("%s",s+1);
n=strlen(s+1);m=122;
get_sa();
for(int i=1;i<=n;i++)
printf("%d ",sa[i]);
printf("\n");
return 0;
}
关于后缀树组的其他定理
\(lcp(sa[i],sa[j])= \min{height[i+1 \to j]}\)
不同子串的个数:\(\frac{n(n+1)}{2}-\sum_{i=2}^{n}{height[i]}\)