模板:后缀数组(SA)

文章目录

  • 前言
  • 解析
    • 后缀排序
      • 优化1:基数排序
      • 优化2:简化第一次排序
      • 优化3:提前break
      • 完整代码
    • LCP与height

所谓后缀数组,就是存储后缀的数组

(逃)

前言

为什么一个算法,如此难以理解却依然是成为一个成熟OIer不可回避的必修课?
足以可见后缀家族功能的强大

首先,由于其本身的性质,后缀数组对字典序相关的问题十分擅长
同时,由于 heightheightheight 数组的众多优秀性质,它在处理公共串问题和 LCP 问题上也十分强大
(我目前SA的题加起来也没做上十道,所以这样的“总结”请选择性阅读)

解析

后缀排序

P3809 【模板】后缀排序

给出一个字符串,把所有后缀按照字典序排序
n≤106n\le10^6n106

考虑倍增
一开始子串长度为 111,每个位置的排名 rkirk_irki 就是自己位置的字符
然后在已知长度为 www 的所有子串的排名的情况下,以 rki+wrk_{i+w}rki+w 为第二关键字,rkirk_irki 为第一关键字排序,可以得到长度为 2w2w2w 的所有子串的排名(空串的排名视为负无穷)
每次用 sort 的话,时间复杂度 O(nlog2n)O(nlog^2n^)O(nlog2n)

优化1:基数排序

注意到这里的排序是关于大小的排序,且值域(排名)只有 O(n)O(n)O(n)
所以我们可以使用基数排序代替 sort,时间复杂度变成 O(nlogn)O(nlogn)O(nlogn)

注意! 基数排序重新排列的循环必须倒序枚举,这样才能保证排序的稳定性

memset(cnt,0,sizeof(cnt));
memcpy(oldrk,rk,sizeof(rk));
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>=1;i--) sa[cnt[rk[id[i]]]--]=id[i];
p=0;
for(int i=1;i<=n;i++){if(oldrk[sa[i]]==oldrk[sa[i-1]]&&oldrk[sa[i]+w]==oldrk[sa[i-1]+w]) rk[sa[i]]=p;else rk[sa[i]]=++p;
}
m=p;

优化2:简化第一次排序

第一次是关于rkiwrk_{i_w}rkiw 排序
并不需要基数排序,只需要:

p=0;
for(int i=n;i>n-w;i--) id[++p]=i;
for(int i=1;i<=n;i++){if(sa[i]>w) id[++p]=sa[i]-w;
}

即可

优化3:提前break

玄学优化
大概就是,不必真的倍增到总长度,只需要让所有字符串的排名互相分开即可
这东西在全是 a 这样的串中可以说是等于没有,但在不少时候优化巨大(比如本题 2.2s→0.8s2.2s\to 0.8s2.2s0.8s)

完整代码

saisa_isai:排名为 iii 的后缀的编号
rkirk_irki:后缀 iii 的排名

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
const int N=1e6+100;
inline ll read(){ll x(0),f(1);char c=getchar();while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}return x*f;
}
int n,m,k;
char s[N];
int rk[N<<1],oldrk[N<<1],id[N],sa[N],cnt[N],p;
void write(int x){if(x>9) write(x/10);putchar('0'+x%10);return;
}
signed main() {
#ifndef ONLINE_JUDGE//freopen("a.in","r",stdin);//freopen("a.out","w",stdout);
#endifscanf(" %s",s+1);n=strlen(s+1);m=122;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>=1;i--) sa[cnt[rk[i]]--]=i;for(int w=1;;w<<=1){p=0;for(int i=n;i>n-w;i--) id[++p]=i;for(int i=1;i<=n;i++){if(sa[i]>w) id[++p]=sa[i]-w;}memset(cnt,0,sizeof(cnt));memcpy(oldrk,rk,sizeof(rk));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>=1;i--) sa[cnt[rk[id[i]]]--]=id[i];p=0;for(int i=1;i<=n;i++){if(oldrk[sa[i]]==oldrk[sa[i-1]]&&oldrk[sa[i]+w]==oldrk[sa[i-1]+w]) rk[sa[i]]=p;else rk[sa[i]]=++p;}m=p;if(m==n) break;//优化3}for(int i=1;i<=n;i++) write(sa[i]),putchar(' ');return 0;
}
/**/

LCP与height

定义:

height(i)height(i)height(i) 表示后缀 saisa_isai 和后缀 sai−1sa_{i-1}sai1 的最长公共前缀(lcp(sai,sai−1)lcp(sa_i,sa_{i-1})lcp(sai,sai1))。特别的,lcp(1)=0lcp(1)=0lcp(1)=0

感性理解来说,把所有后缀按照字典序排序后,height(i)height(i)height(i) 就是相邻两个后缀的相同部分的长度。

引理1:lcp(i,j)=min⁡(lcpi,k,lcpk,j)lcp(i,j)=\min (lcp_{i,k},lcp_{k,j})lcp(i,j)=min(lcpi,k,lcpk,j),对于任意的 i≤k≤ji\le k\le jikj 均成立.

证明:
首先,min⁡(lcpi,k,lcpk,j)\min (lcp_{i,k},lcp_{k,j})min(lcpi,k,lcpk,j)kkki,ji,ji,j 共同的公共前缀,所以也必然是 i,ji,ji,j 的公共前缀,lcp(i,j)≥min⁡(lcpi,k,lcpk,j)lcp(i,j)\ge\min (lcp_{i,k},lcp_{k,j})lcp(i,j)min(lcpi,k,lcpk,j)
同时,由于字典序单调的性质,iii 变到 kkk 变化的前缀在 kkk 变化到 jjj 时必然不可能再变回来,否则 jjj 的字典序就比 kkk 小了,所以有 lcp(i,j)≤min⁡(lcpi,k,lcpk,j)lcp(i,j)\le\min (lcp_{i,k},lcp_{k,j})lcp(i,j)min(lcpi,k,lcpk,j)
综上,lcp(i,j)=min⁡(lcpi,k,lcpk,j)lcp(i,j)=\min (lcp_{i,k},lcp_{k,j})lcp(i,j)=min(lcpi,k,lcpk,j),证毕。

引理2:heightrki≥heightrki−1−1height_{rk_i}\ge height_{rk_{i-1}}-1heightrkiheightrki11

证明:
rki−1≤1rk_{i-1}\le1rki11 时,显然成立
rki−1>1rk_{i-1}>1rki1>1 时,设 rki−1−1=krk_{i-1}-1=krki11=kkkk 就是 i−1i-1i1 按字典序排序后的前一个),那么:
i−1i-1i1kkk 的首字母不同, hi−1=0h_{i-1}=0hi1=0 ,显然成立
i−1i-1i1kkk 的首字母相同,那么考虑字符串 k+1k+1k+1,由于k 去掉首字符变成 k+1,i-1 去掉首字母变成 i,所以 k+1k+1k+1 也一定在 iii 的前面,同时 lcp(k+1,i)=lcp(k,i−1)−1=heightrki−1−1lcp(k+1,i)=lcp(k,i-1)-1=height_{rk{i-1}}-1lcp(k+1,i)=lcp(k,i1)1=heightrki11,由引理1,有 lcp(k+1,i)=min⁡(lcp(k+1,rki−1),lcp(i−1,i))lcp(k+1,i)=\min (lcp(k+1,rk_i-1),lcp(i-1,i))lcp(k+1,i)=min(lcp(k+1,rki1),lcp(i1,i)),故 lcp(i−1,i)≥heightrki−1−1lcp(i-1,i)\ge height_{rk{i-1}}-1lcp(i1,i)heightrki11,即 heightrki≥heightrki−1−1height_{rk_i}\ge height_{rk_{i-1}}-1heightrkiheightrki11
得证。
得出这个性质后,线性求 heightheightheight 的代码就不难写出了:

for(int i=1,k=0;i<=n;i++){if(k) --k;while(s[i+k]==s[sa[rk[i]-1]+k]) ++k;ht[rk[i]]=k;
}

Thanks for reading!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/317287.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

P3959 [NOIP2017 提高组] 宝藏

P3959 [NOIP2017 提高组] 宝藏 题意: 额题意不好说&#xff0c;就是n个点m个边&#xff0c;选定一个点为根节点&#xff0c;构造一个最小生成树&#xff0c;边的权值为该该边起点到根节点之间的点的数量K&#xff08;不含根节点&#xff09; * 道路长度 1<n<12 0<m&…

如何在ASP.NET Core程序启动时运行异步任务(3)

原文&#xff1a;Running async tasks on app startup in ASP.NET Core (Part 3)作者&#xff1a;Andrew Lock译者&#xff1a;Lamond Lu之前我写了两篇有关在ASP.NET Core中运行异步任务的博文&#xff0c;本篇博文是对之前两篇博文中演示示例和实现方法的简短跟进。你可以通过…

CF1140G-Double Tree【最短路,矩阵乘法,树上倍增】

正题 题目链接:https://www.luogu.com.cn/problem/CF1140G 题目大意 给出一个nnn个点的树TTT&#xff0c;然后复制一份T′TT′&#xff0c;每个TTT中的点iii向T′TT′中的点iii都有连边构成一张图。 图上所有权值各不相同&#xff0c;现在qqq次询问图上两点的最短路。 1≤n≤…

数论六之计算几何——An Easy Problem,Ancient Berland Circus,Open-air shopping malls

可检验模板正确度An Easy Problem?!Ancient Berland CircusOpen-air shopping mallsAn Easy Problem?! problem 就是大讨论 #include <cmath> #include <cstdio> #include <iostream> using namespace std; #define eps 1e-6struct vec {double x, y;ve…

CodeForces:12271261(div1)1262(div2)

文章目录前言CF1227A Math ProblemDescription\text{Description}DescriptionSolution\text{Solution}SolutionCode\text{Code}CodeCF1227B BoxDescription\text{Description}DescriptionSolution\text{Solution}SolutionCode\text{Code}CodeCF1227C MessyDescription\text{Des…

【NET CORE微服务一条龙应用】应用部署

简介本章主要介绍https://github.com/q315523275/FamilyBucket上微服务一条龙应用&#xff0c;在实际使用中的应用部署&#xff0c;以原始方式部署非docker部署应用主要包括&#xff1a;1、网关应用部署2、授权认证应用部署3、配置中心查询服务端应用部署4、综合管理应用部署5、…

牛客网区间dp练习

NC13230 合并回文子串 NC16129 小小粉刷匠 NC19909 [CQOI2007]涂色PAINT NC19997 [HAOI2016]字符合并 NC20238 [SCOI2003]字符串折叠 NC20252 [SCOI2007]压缩 NC20312 [SDOI2008]SUE的小球 POJ3042 Grazing on the Run

CF516D-Drazil and Morning Exercise【树上差分,倍增】

正题 题目链接:https://www.luogu.com.cn/problem/CF516D 题目大意 给出一棵nnn个点的树&#xff0c;定义f(x)f(x)f(x)表示距离点xxx最远的点的距离&#xff0c;qqq次询问给出一个kkk&#xff0c;要求一个最大的连通块满足连通块中所有点的f(x)f(x)f(x)最大最小差值不能超过k…

容斥问卷调查反馈——Co-prime,Character Encoding,Tree and Constraints,「2017 山东一轮集训 Day7」逆序对

文章目录Co-primesourcesolutioncodeCharacter EncodingsourcesolutioncodeTree and Constraintssourcesolutioncode「2017 山东一轮集训 Day7」逆序对sourcesolutioncodeCo-prime source TTT组数据&#xff0c;给出&#x1d43f;,&#x1d445;,&#x1d441;&#x1d43f;, …

手工修复Azure DevOps无法连接到Azure的问题

点击上方蓝字关注“汪宇杰博客”今天我在为一个从TFVC迁移到Git的老项目重新配置发布到Azure App Service的CI/CD管线的时候&#xff0c;Azure DevOps竟然爆了。这是一个微软已知的bug&#xff0c;目前还未修复&#xff0c;我来带大家看看如何手工workaround这个问题。首先&…

Loj#576-「LibreOJ NOI Round #2」签到游戏【线段树】

正题 题目链接:https://loj.ac/p/576 题目大意 给出一个长度为nnn的序列aaa&#xff0c;还有一个未知序列bbb&#xff0c;你每次可以花费gcd⁡ilrai\gcd_{il}^r a_igcdilr​ai​的代价得到∑ilrbi\sum_{il}^rb_i∑ilr​bi​的值。 每次修改aaa中的一个数&#xff0c;求得到b…

NC14732 锁

NC14732 锁 题意&#xff1a; n个居民&#xff0c;门上有k把锁&#xff0c;每个居民有若干钥匙&#xff0c;为1到k的一个子集&#xff0c;如果几名居民的钥匙的并集是1到k&#xff0c;即他们拥有全部锁的对应钥匙。 求最小的k&#xff0c;使得可以适当地给居民们每人若干钥匙…

CodeForces:1103(div1)1104(div2)

文章目录前言CF1104A Splitting into digitsDescription\text{Description}DescriptionSolution\text{Solution}SolutionDescription\text{Description}DescriptionCF1104B Game with stringDescription\text{Description}DescriptionSolution\text{Solution}SolutionCode\text…

专题突破一之分块——Untitled Problem II,Balanced Lineup,[ioi2009]Regions

文章目录SP2940 UNTITLE1 - Untitled Problem IIsourcesolutioncodeBalanced LineupsourcecodeCount on a tree II[ioi2009]RegionsSP2940 UNTITLE1 - Untitled Problem II source solution 分块 si{sik(i−l1)sikik(1−l)l≤i≤rsik(r−l1)r<is_i\begin{cases} s_ik\tim…

.NET Core实战项目之CMS 第十七章 CMS网站系统的部署

目前我们的.NET Core实战项目之CMS系列教程基本走到尾声了&#xff0c;通过这一系列的学习你应该能够轻松应对.NET Core的日常开发了&#xff01;当然这个CMS系统的一些逻辑处理还需要优化&#xff0c;如没有引入日志组件以及缓存功能&#xff0c;权限目前只支持控制到菜单&…

ICPC2019南昌区域赛

ICPC2019南昌区域赛 题号题目知识点难度A9102BA Funny Bipartite Graph状压dp思维稳银快金CAnd and Pair二项式定理快铜DBitwise TreeEBob’s Problem思维&#xff0c;生成树签到FDynamic Suffix ArrayGEating Plan思维题稳铜快银HPowers of TwoIResistanceJSummonKTreeLWho i…

Loj#510-「LibreOJ NOI Round #1」北校门外的回忆【线段树】

正题 题目链接:https://loj.ac/p/510 题目大意 给出一个代码 function add(x,v)while x < n dos[x] s[x] xor vx x lowbit(x) //注意&#xff0c;这里是 lowbit&#xff0c;这也是两份代码唯一的区别end while end functionfunction query(x)ans 0while x > 0 doa…

如何用EFCore Lazy Loading实现Entity Split

α角 与 β角支持 现实生活 的 计算机系统&#xff0c;总有着两大偏差&#xff0c;第一个是 现实生活 与 计算机系统 的α角&#xff0c;另外一个是计算机系统的 逻辑设计 与 物理设计 的β角。举个栗子&#xff1a;α角&#xff1a;假设某个公司的商业流程&#xff0c;我们在做…

CodeForces:372(div1)div373(div2)

文章目录前言CF373A Collecting Beats is FunDescription\text{Description}DescriptionSolution\text{Solution}SolutionCode\text{Code}CodeCF373B Making Sequences is FunDescription\text{Description}DescriptionSolution\text{Solution}SolutionCF372A Counting Kangaro…

一二三系列之CodeChef分块——Chef and Churu,Chef and Problems,Children Trips

文章目录Chef and ChurusourcesolutioncodeChef and ProblemssourcesolutioncodeChildren TripssourcesolutioncodeChef and Churu source solution 对于单独的iii&#xff0c;查询可以用线段树/树状数组O(nlog⁡n)O(n\log n)O(nlogn)&#xff0c;这暗示可以平衡查询修改次数…