后缀树组 学习笔记
0xFF 一些备注
本篇博客所有证明基本略过,主要总结后缀树组的应用
引用有 [2009]后缀数组——处理字符串的有力工具 by. 罗穗骞、OI wiki、日报 的大量内容
实现方面本篇博客只会倍增方法
0x00 一些定义
-
第 个后缀指的是首字符在 位置的后缀
-
这里排序的关键字是字典序,定义空字符最小
-
表示排名为 的后缀是第几个后缀
-
表示第 个后缀的排名是多少
-
表示后缀 和后缀 的最长公共前缀长度
-
表示
0x01 后缀排序
目标:将一个字符串的 个后缀按照字典序大小排序
实现:按照倍增的思想,分别排序前 个字符。上一次排序过后,以上一次排序为第二关键字,当前层为第一关键字继续排序,总复杂度
至于怎样 排序,可以采用基数排序,因为上一层可以顺便帮助这一层离散化
具体来讲,开 个桶,将值放进去,然后自然而然就排好序了
直接放代码,照着代码来具体讲:
void get_sa(){
for(int i=1;i<=n;i++)c[x[i]=s[i]]++;
for(int i=1;i<=m;i++)c[i]+=c[i-1];
for(int i=n;i>=1;i--)sa[c[x[i]]--]=i;
for(int k=1;k<=n;k<<=1){
int 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;
}
for(int i=1;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],y[i]=0;
swap(x,y);
x[sa[1]]=1;
num=1;
for(int i=2;i<=n;i++){
x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
}
if(num==n)break;
m=num;
}
return ;
}
int main(){
m=122;
scanf("%s",s+1);
n=strlen(s+1);
get_sa();
}
和 数组有关的是基数排序的过程,不再赘述
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;
}
这两行的意思是这样的,由于从 开始,由于长度不够,所以子串为空,自然最短,而剩下的则有 决定,这样省去了第二关键字排序的过程
0x02 lcp问题的求解
直接放一些我也不会证的结论:
利用最后一个结论,可以快速 求出 数组
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;
}
return ;
}
以上就是后缀数组题板子的标配,目前还没有见过需要动板子的题,背会就行了
0x03 简单应用
以下开始疯狂抄袭论文例题:
可重叠最长重复子串
给定一个字符串,求最长重复子串,这两个子串可以重叠。
由于后缀排完序以后重复子串一定是在相邻后缀中的,求最长,是经典的 问题,找到最大的 值即可
不可重叠最长重复子串
给定一个字符串,求最长重复子串,这两个子串不能重叠。
首先二分答案,然后这里用到一个非常有用的套路:
由于求解 问题是一直取 的过程,一旦有一组极小值产生了断层,则无法挽回
既然二分答案后子串的长度已经确定,那么将 小于答案的地方隔断,将后缀们分成若干组,这样在一组内的后缀只要不重叠就满足条件
对于重叠的判断:每一组内找出实际串里最靠前和最靠后的位置,即 的最值,若差比答案大则没有重叠
从这个题里可以引申出另一道的做法:
首先这里的相似即是 运算,可以按照类似的方法分组,在一组里面的都是满足相似关系的
考虑多组询问,如果按照题目的顺序,当相似值加一时,意味着有新的位置不满足条件需要拆开,而拆开这个操作是不好维护的
于是用倒序,则变成合并,转化成一个联通性问题,用并查集维护
对于第二问,并查集顺便统计连通块最值,由于有负权值,需要分类讨论负数的情况
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=3e5+5;
const int inf=0x3f3f3f3f3f3f3f3f;
int n,m,c[maxn],sa[maxn],x[maxn],y[maxn],rk[maxn],height[maxn],fa[maxn],siz[maxn],sum,val=-inf,a[maxn],mx[maxn],cmx[maxn],mn[maxn],cmn[maxn];
vector<int>ex[maxn];
pair<int,int>ans[maxn];
char s[maxn];
int read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')f=-1;
ch=getchar();
}
while(isdigit(ch)){
x=x*10+ch-48;
ch=getchar();
}
return x*f;
}
void get_sa(){
for(int i=1;i<=n;i++)c[x[i]=s[i]]++;
for(int i=2;i<=m;i++)c[i]+=c[i-1];
for(int i=n;i;i--)sa[c[x[i]]--]=i;
for(int k=1;k<=n;k<<=1){
int 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;
}
for(int i=1;i<=m;i++)c[i]=0;
for(int i=1;i<=n;i++)c[x[i]]++;
for(int i=2;i<=m;i++)c[i]+=c[i-1];
for(int i=n;i;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0;
swap(x,y);
x[sa[1]]=1;
num=1;
for(int i=2;i<=n;i++){
x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
}
if(num==n)break;
m=num;
}
return ;
}
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;
}
return ;
}
int find(int x){
return fa[x]==x?x:fa[x]=find(fa[x]);
}
int calc_val(int x){
return x*(x-1)/2;
}
void calc(int p){
for(int i=0;i<ex[p].size();i++){
int x=ex[p][i];
int xx=find(x-1);
int yy=find(x);
sum+=calc_val(siz[xx]+siz[yy])-calc_val(siz[xx])-calc_val(siz[yy]);
fa[xx]=yy;
siz[yy]+=siz[xx];
int d[6]={-inf,mx[xx],cmx[xx],mx[yy],cmx[yy]};
sort(d+1,d+5);
// for(int i=1;i<=4;i++)cout<<d[i]<<" ";
// cout<<endl;
mx[yy]=d[4];
cmx[yy]=d[3];
int b[6]={-inf,mn[xx],cmn[xx],mn[yy],cmn[yy]};
sort(b+1,b+5);
// for(int i=1;i<=4;i++)cout<<b[i]<<" ";
// cout<<endl;
mn[yy]=b[1];
cmn[yy]=b[2];
val=max(val,max(mx[yy]*cmx[yy],mn[yy]*cmn[yy]));
}
ans[p]=make_pair(sum,val);
if(val==-inf)ans[p].second=0;
return ;
}
signed main(){
n=read();
scanf("%s",s+1);
for(int i=1;i<=n;i++)a[i]=read();
m=122;
get_sa();
get_height();
for(int i=2;i<=n;i++)ex[height[i]].push_back(i);
for(int i=1;i<=n;i++){
fa[i]=i;
siz[i]=1;
mx[i]=mn[i]=a[sa[i]];
cmx[i]=-inf;
cmn[i]=inf;
}
for(int i=n-1;i>=0;i--)calc(i);
for(int i=0;i<n;i++)printf("%lld %lld\n",ans[i].first,ans[i].second);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】