SA 学习笔记
后缀数组
后缀数组,维护的是原字符串的每一个后缀,将其按照字典序大小排序,得到一些有用的信息。
首先明确几个数组的定义:
sa[] //sa[i] 表示字典序第 i 小的后缀在原串中的起始位置为 sa[i]
rk[] //rk[i] 表示原串从 i 位置开始的后缀的字典序的排名
h[] //h[i] 表示排名为第 i 的后缀与排名为第 i-1 的后缀的最长公共前缀
接下来我们考虑每个数组要怎么求解。
求解
对于
具体的,对于每一个位置
特别的,对于
求解
求完
sa[rk[i]]=i
求解
- 排名为
的后缀与排名为 的后缀的最长公共前缀
第一条性质的正确性是显然的。
我们重点来关注第二条。
我们设
- 若
,结论显然成立 - 若
,此时 ,我们把两个串的首位丢掉,也就是 和 这两个串,因此 。由于 排在 前面,因此 也排在 前面,根据第一条性质,我们可以得到 ,所以 。得证。
根据第二条性质,我们可以按照
code
int flag=0,rk[N],sa[N],h[N],fi[N],se[N],b[N],g[N],tp[155];
inline void Round(int *a) {
int mx=0;
for(int i=1;i<=n;i++) mx=max(mx,a[i]);
for(int i=0;i<=mx;i++) b[i]=0;
for(int i=1;i<=n;i++) b[a[i]]++;
for(int i=1;i<=mx;i++) b[i]+=b[i-1];
for(int i=n;i>=1;i--) rk[g[i]]=b[a[g[i]]]--;
for(int i=1;i<=n;i++) g[rk[i]]=i;
}
inline void Qsort() {
int idx=1;
for(int i=1;i<=n;i++) rk[i]=g[i]=i;
Round(se); Round(fi);
for(int i=2;i<=n;i++)
rk[g[i]]=(fi[g[i]]==fi[g[i-1]] && se[g[i]]==se[g[i-1]])?rk[g[i-1]]:++idx;
if(idx==n) flag=1;
}
inline void Getsa() {
mem(tp); for(int i=1;i<=n;i++) tp[s[i]-'a']=1;
for(int i=1;i<155;i++) tp[i]+=tp[i-1];
for(int i=1;i<=n;i++) rk[i]=tp[s[i]-'a'];
for(int i=1;i<=n && !flag;i<<=1) {
for(int j=1;j<=n;j++)
fi[j]=rk[j], se[j]=j+i>n?0:rk[j+i];
Qsort();
}
for(int i=1;i<=n;i++) sa[rk[i]]=i;
}
inline void Geth() {
int k=0;
for(int i=1,j;i<=n;i++) {
if(k) --k;
j=sa[rk[i]-1];
while(s[i+k]==s[j+k] && s[i+k]!='|') k++;
h[rk[i]]=k;
}
}
板子是根据自己的理解写出来的,比较屑,常数也比较大。
一些经典的套路、例题
P2178 [NOI2015] 品酒大会
对于这一类的题目,可以对于
[NOI2016] 优秀的拆分
对于这一类,求解与连续重复
具体的
我们从小到大枚举循环节的长度
假设当前枚举到了关键点
我们设
首先为了限制左端点的位置,我们令
于是我们考虑的这些串中最大循环次数为
最后再单独考虑循环次数为
这种方法既可以用于求最值,也可以用来求方案数,时间复杂度
P4248 [AHOI2013]差异
统计每一个子串作为
P4070 [SDOI2016]生成魔咒
动态维护
其余,待更.......
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现