后缀数组 (SA) 学习笔记
写得很草率的一篇东西。
后缀排序
#include<bits/stdc++.h>
#define il inline
using namespace std;
il int read()
{
int xr=0,F=1;char cr=getchar();
while(cr<'0'||cr>'9') {if(cr=='-') F=-1;cr=getchar();}
while(cr>='0'&&cr<='9')
xr=(xr<<3)+(xr<<1)+(cr^48),cr=getchar();
return xr*F;
}
const int N=2e6+5;
int n,m;
char s[N];
int sum[N],sa[N],tp[N],rk[N];
//编号:后缀 (i~n) 的编号为 i
//sa[i] (上一轮)排名为 i 的后缀的编号
//rk[i] (上一轮)编号为 i 的串的排名
//tp[i] 第二关键字排名为 i 的串的编号
void qsort()
{
//sum[i] 桶,存 rk[x]=i 的 x 数量
for(int i=0;i<=m;i++) sum[i]=0;//清空桶
for(int i=1;i<=n;i++) sum[rk[i]]++;
for(int i=1;i<=m;i++) sum[i]+=sum[i-1];//前缀和,此时 sum[i] 表示 rk[x]<=i 的 x 数量
//所以 sum[i] 就是 rk[x]=i 的这个 x 排序后的排名
for(int i=n;i;i--) sa[sum[rk[tp[i]]]--]=tp[i];
//这句话到底在干啥。
//因为 sum[i] 是所有 rk[x]=i 中最后一个位置,要倒序往里填
//倒序枚举 i,实际求的是 tp[i] 的排名,这样枚举是保证第二关键字降序
//rk[tp[i]] 为 (第二关键字排名为 i 的串)的 第一关键字排名
//sum[rk[tp[i]]],现在要填的位置,填完后 sum--,代表下次往前一个位置填
//sa[sum[rk[tp[i]]]] 现在位置是 sum[rk[tp[i]]] 的串,它的编号是 tp[i]
}
int main()
{
scanf("%s",s+1);
n=strlen(s+1),m=200;
for(int i=1;i<=n;i++) rk[i]=s[i],tp[i]=i;
//初始排名,第一关键字是起始字符,第二关键字是位置
qsort();
for(int w=1,tot=0;tot<n;m=tot,w<<=1)
{
//tot 当前有多少种不同的排名,如果已经有 n 个代表所有串都排出来了,退出
//w 当前倍增的长度,即 rk 和 tp **分别** 已经排完的长度
//实际上是在求长度为 2w 时的答案
tot=0;
for(int i=n-w+1;i<=n;i++) tp[++tot]=i;
//[n-w+1,n] 的长度不足 w,第二关键字为 0,按起始位置排名
for(int i=1;i<=n;i++)
if(sa[i]>w) tp[++tot]=sa[i]-w;
//按第二关键字的排名从小到大枚举串,sa[i] 即编号为 sa[i]-w 的串的第二关键字
qsort();swap(tp,rk);
//现在的 sa 已经更新成长度为 2w 的答案了,但 rk 没更新
//把长度为 w 时的答案备份进 tp 里,因为原来的 tp 没用了
//现在的 tp[i] 表示 上一轮排序中 编号为 i 的串的排名
tot=rk[sa[1]]=1;
//tot 表示当前的排名
for(int i=2;i<=n;i++)
//枚举新排名为 i 的串,如果它前一半和后一半都和上一个串的旧排名一样,它们新排名还是一样
//否则排名是上一个串+1
rk[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]&&tp[sa[i]+w]==tp[sa[i-1]+w])?tot:++tot;
//这里面 rk[i] 是可以重复的,但 sa[i] 不可以,一定要绕明白(
}
for(int i=1;i<=n;i++) printf("%d ",sa[i]);
return 0;
}
struct 封装版(方便复制)
struct suffix_array
{
int n,m,h[N],sa[N],rk[N],tp[N],sum[N];
char s[N];
il void qsort()
{
for(int i=1;i<=m;i++) sum[i]=0;
for(int i=1;i<=n;i++) sum[rk[i]]++;
for(int i=1;i<=m;i++) sum[i]+=sum[i-1];
for(int i=n;i;i--) sa[sum[rk[tp[i]]]--]=tp[i];
}
il void SA()
{
for(int i=1;i<=n;i++) rk[i]=s[i],tp[i]=i;
m=200,qsort();
for(int w=1,tot=0;tot<n;m=tot,w<<=1)
{
tot=0;
for(int i=n-w+1;i<=n;i++) tp[++tot]=i;
for(int i=1;i<=n;i++) if(sa[i]>w) tp[++tot]=sa[i]-w;
qsort(),swap(rk,tp); rk[sa[1]]=tot=1;
for(int i=2;i<=n;i++)
rk[sa[i]]=(tp[sa[i]]!=tp[sa[i-1]]||tp[sa[i]+w]!=tp[sa[i-1]+w])?++tot:tot;
}
for(int i=1,now=0;i<=n;i++)
{
if(now) now--;
while(s[i+now]==s[sa[rk[i]-1]+now]) now++;
h[rk[i]]=now;
}
}
};
例题
P4051 [JSOI2007] 字符加密
SA 板子,把 s 复制两遍断环为链,再后缀排序。
height 数组
定义
\(height[i]=lcp(sa[i],sa[i-1])\),其中 \(height[1]=0\)。
性质
\(height[rk[i]]\ge height[rk[i-1]]-1\)。
求法
根据上面的性质即可 \(O(n)\) 求出。
void get_hgt()
{
for(int i=1,now=0;i<=n;i++)
{
if(now) now--;
while(s[i+now]==s[sa[rk[i]-1]+now]) now++;
hgt[rk[i]]=now;
}
}
例题
P2852 [USACO06DEC]Milk Patterns G
题意简述:给一个字符串,求至少出现 \(k\) 次的串的最长长度。
Solution:
一个串至少出现 \(k\) 次,则至少出现在 \(k\) 个后缀的 LCP 中。
所以答案是 所有连续 \(k-1\) 个 height 的最小值中的最大值。
单调队列即可做到 \(O(n)\)。
P2743 [USACO5.1]乐曲主题Musical Themes
题意简述:找最长的 \(s\),满足 \(s\) 在字符串中至少不重叠地出现了两次。
Solution:
二分答案 \(mid\),将 height 数组划分成连续的几段,其中每一段除第一个外 height 都>=mid。
求出每一段 \(sa[i]\) 的最大最小值,若 \(mx-mn>=mid\),则 \(mid\) 合法,两个串起始位置分别为 mx 和 mn。
在本题中,由于求的是差分数组,mx-mn 不取等号。
POJ 1226/ybt 例2
题意简述:给定 \(n\) 个字符串,求一个最长的字符串,要求其在 \(n\) 个串或者它们翻转后的串中出现过。
Solution:
把这 \(n\) 个串和它们反转后的串连在一起,中间用不同且没出现过的字符隔开。
二分答案将 \(height[i]\) 分段,若有一段包含了这 \(n\) 个串,答案合法。用 set 维护。
后缀数组 (SA) 学习笔记的更多相关文章
- 后缀数组SA学习笔记
什么是后缀数组 后缀数组\(sa[i]\)表示字符串中字典序排名为\(i\)的后缀位置 \(rk[i]\)表示字符串中第\(i\)个后缀的字典序排名 举个例子: ababa a b a b a rk: ...
- 洛谷.3809.[模板]后缀排序(后缀数组 倍增) & 学习笔记
题目链接 //输出ht见UOJ.35 #include<cstdio> #include<cstring> #include<algorithm> const in ...
- 后缀数组SA入门(史上最晦涩难懂的讲解)
参考资料:victorique的博客(有一点锅无伤大雅,记得看评论区),$wzz$ 课件(快去$ftp$%%%),$oi-wiki$以及某个人的帮助(万分感谢!) 首先还是要说一句:我不知道为什么我这 ...
- 后缀数组(SA)总结
后缀数组(SA)总结 这个东西鸽了好久了,今天补一下 概念 后缀数组\(SA\)是什么东西? 它是记录一个字符串每个后缀的字典序的数组 \(sa[i]\):表示排名为\(i\)的后缀是哪一个. \(r ...
- [笔记]后缀数组SA
参考资料这次是真抄的: 1.后缀数组详解 2.后缀数组-学习笔记 3.后缀数组--处理字符串的有力工具 定义 \(SA\)排名为\(i\)的后缀的位置 \(rk\)位置为\(i\)的后缀的排名 \(t ...
- bzoj3796(后缀数组)(SA四连)
bzoj3796Mushroom追妹纸 题目描述 Mushroom最近看上了一个漂亮妹纸.他选择一种非常经典的手段来表达自己的心意——写情书.考虑到自己的表达能力,Mushroom决定不手写情书.他从 ...
- SA 学习笔记
后缀数组是解决字符串问题的有力工具--罗穗骞 后缀数组是对字符串的后缀排序的一个工具, sa将排名为i的字符串的开头位置记录下来, rnk将开头位置为i的字符串的排名记录下来. https://www ...
- 【字符串】后缀数组SA
后缀数组 概念 实际上就是将一个字符串的所有后缀按照字典序排序 得到了两个数组 \(sa[i]\) 和 \(rk[i]\),其中 \(sa[i]\) 表示排名为 i 的后缀,\(rk[i]\) 表示后 ...
- 浅谈后缀数组SA
这篇博客不打算讲多么详细,网上关于后缀数组的blog比我讲的好多了,这一篇博客我是为自己加深印象写的. 给你们分享了那么多,容我自私一回吧~ 参考资料:这位dalao的blog 一.关于求Suffix ...
- 后缀自动机SAM学习笔记
前言(2019.1.6) 已经是二周目了呢... 之前还是有一些东西没有理解到位 重新写一下吧 后缀自动机的一些基本概念 参考资料和例子 from hihocoder DZYO神仙翻译的神仙论文 简而 ...
随机推荐
- 【SpringBoot】整合Redis
1.前言 最近公司在做项目,用到了redis,,发现自己一点都不会,然后就乘闲暇时间,自己学习一些redis相关的知识,在这里分享给像我一样的初学者. 2.我的项目结构: 2.1 pom.xml &l ...
- Flutter 屏幕采集如何实现(提供示例demo)
在视频会议.线上课堂.游戏直播等场景,屏幕共享是一个最常见的功能.屏幕共享就是对屏幕画面的实时共享,端到端主要有几个步骤:录屏采集.视频编码及封装.实时传输.视频解封装及解码.视频渲染.一般来说,实时 ...
- 《逆向工程核心原理》之DLL注入
DLL注入 DLL注入指的是向运行中的其他进程强制插入特定的DLL文件.从技术细节来说,DLL注入命令其他进程自行调用LoadLibrary() API,加载(Loading)用户指定的DLL文件.D ...
- std::ofstream 写本地音频
最近线上 PK 偶然出现双方主播互相听不见声音的情况,在日志不能明确体现问题时,就需要抓下主播本地的音频和远端的音频来确定数据是在哪消失的 所以我们用到一个比较简单的流写出的标准库类:std::ofs ...
- C#.NET 国密SM4对称加解密 与JAVA互通 ver:20230731
C#.NET 国密SM4对称加解密 与JAVA互通 ver:20230731 .NET 环境:.NET6 控制台程序(.net core). JAVA 环境:JAVA8,带maven 的JAVA控制台 ...
- C++欧几里得算法求最大公约数和最小公倍数
定义 最大公约数即为 Greatest Common Divisor,常缩写为 gcd. 一组整数的公约数,是指同时是这组数中每一个数的约数的数. 一组整数的最大公约数,是指所有公约数里面最大的一个. ...
- PXE操作过程 kickstart 无人值守安装
PXE操作过程 分配给同一局域网内新加机器的地址(配置文件) dhcp 分配地址 指明tftp 服务器的地址 tftp服务端开启 udp 配置 默认关闭 安装syslinux 取得 pxelinux. ...
- 数仓备份经验分享丨详解roach备份原理及问题处理套路
本文分享自华为云社区<GaussDB(DWS) 备份问题定位思路>,作者: yd_216390446. 前言 在数据库系统中,故障分为事务内部故障.系统故障.介质(磁盘)故障.对于事务内部 ...
- Vue【原创】可拖动列表 darg-list
可拖动排序的列表 drag-list,这个比较简单易懂,拿例子直接运行看效果就好了. 组件代码: 1 <template> 2 <ul class="list" ...
- PyAV 使用浅谈
背景: PyAV是一个用于音频和视频处理的Python库,它提供了一个简单而强大的接口,用于解码.编码.处理和分析各种音频和视频格式.PyAV基于FFmpeg多媒体框架,它本质上是FFmpeg 的Py ...