后缀数组(SA)
终于刷完网络流后准备继续做sa,发现自己忘完了,于是来写个博客。
应用
用
概念
两个数组:
满足性质:
一定一定要清楚区分两个数组,因为经常会嵌套使用,如
oi-wiki的字符串太长了,不方便手模,所以手绘了一个
原理
主要是倍增的思想
对于两个串
当排名各不相同时跳出。得到
代码
前方大量注释来袭
int p,rk[MAX],sa[MAX],cnt[MAX],id[MAX],ork[MAX],s[MAX];
inline bool cmp(int x,int y,int w){
return ork[x]==ork[y]&&ork[x+w]==ork[y+w];
};
inline void SA(int n){
m=128;//字符串内容的最大值
for(int i=1;i<=n;++i) cnt[rk[i]=s[i]]++;
for(int i=1;i<=m;++i) cnt[i]+=cnt[i-1];
for(int i=n;i;--i) sa[cnt[rk[i]]--]=i;
//O(n)基数排序
//与上图的串为例,rk 97 97 98 97 98,sa 1 2 4 3 5
for(int j=1;p!=n;j<<=1,m=p){
//p为排名数组中的最大值,当p==n时排序完成
//j为上次排序长度
p=0;
//当前串已排好长度为j的序,即第一关键字是有序的,那么只需排序第二关键字。实质是将超出字符串范围的后缀放到同第一关键字的最前,即一起放在基数排序的前面
for(int i=n;i>n-j;--i) id[++p]=i;
//没超出的依次放入
for(int i=1;i<=n;++i)
if(sa[i]>j) id[++p]=sa[i]-j;
//第二次排序中,id 5 1 3 2 4
memset(cnt,0,(m+1)*sizeof(int));
for(int i=1;i<=n;++i) cnt[rk[id[i]]]++;
for(int i=1;i<=m;++i) cnt[i]+=cnt[i-1];
for(int i=n;i;--i) sa[cnt[rk[id[i]]]--]=id[i];
//id中5在3前,所以基数排序中从后往前扫,从后往前放,于是sa中5在3前。sa 1 2 4 5 3
//此时只排了第二关键字为0的后缀,sa不完全是此次排序后的sa
memcpy(ork,rk,sizeof(rk));p=0;
for(int i=1;i<=n;++i)
rk[sa[i]]=cmp(sa[i],sa[i-1],j)?p:++p;
//离散化,第一或第二关键字不一样就和上一个区分开,更新rk和p,如上图第二次排序rk 1 2 4 2 3,p=4
}for(int i=1;i<=n;++i) sa[rk[i]]=i;
//根据rk更新最终sa
}
代码不好背也不好理解,可以多手模或者干脆所学几遍
height数组
LCP
height
lemma
证明
当当
后缀i是后缀i-1的后缀,设后缀i-1为"
设后缀
又因为后缀i与它排名相邻的字符串相似度最高,所以
由以上引理可以暴力移动k(后缀i与排名上一名的lcp),其下降不超过n次,最大不超过n,所以最多移动2n次,即复杂度
代码
inline void getth(){
int k=0;
for(int i=1;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;
}
}
定理
两子串最长公共前缀
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)