后缀数组 SA

后缀数组 SA

后缀树组(SA,suffix array),用于处理字符串子串形成的结构。

处理子串的结构主要方式有:后缀数组 SA,后缀自动机 SAM,后缀树 ST

后缀树后缀自动机暂时决定咕咕咕,以后学习可以参考ix35 的字符串复习。

含义与实现

后缀

我们定义长度为 \(n\) 的字符串 \(s\) 编号为 \(l\) 的后缀为 \(s\)\([l,s]\) 上的字串,记 \(l\)(即起始位置)为该后缀的编号

后缀树组

如果将 \(s\) 所有 \(n\) 个后缀提取出来,按照字典序升序排序,后缀数组 \(\text{SA}_i\) 定义为排名为 \(i\) 的后缀的编号

那么怎么求出一个字符串的后缀数组呢?

求解

显然暴力找出 \(n\) 个后缀再快速排序的 \(\mathcal{O(n^2\log n)}\) 的(加上比较的复杂度),不够优美。

一般采用 Manber 和 Myers 发明的类似于倍增的方法求解。

(下面图片来自jinkun113 - 后缀数组,如有侵权请告知删除)

如上图,类似每次将两端字符串拼起来形成新的字符串,类似高低进制拼接后再一次排序得到。

通过这样的操作,以 \(i\) 为开头的字串会不断增长到两倍(大于 \(n\) 的用空字符补齐),即不断溢出,最终就得到了 \(n\) 个后缀。

类似这样的倍增求解的方法还可以用于其他的问题,如CF1654F Minimal String Xoration。

实现

在上面的过程中,记在 \(i\) 位置上合并的两个数高位的是 \(x_i\),低位的是 \(y_i\),他们记录的是这一部分字串的排名,最终再一次排序后的排名记在 \(x\) 数组内。

这样非常容易写出一个 \(\mathcal{O(n\log^2 n)}\) 的解法,具体就是每次将两端字串合并后,给 \(i\) 赋值为 \(x_i,y_i\) 高低位相接。之后再一次排序并离散化就可以得到新的排名。

//暴力O(n\log^2 n)
int MAX;
int sa[Maxn],First[Maxn],Second[Maxn];
bool cmp(int x,int y)
{if(First[x]==First[y]) return Second[x]<Second[y];return First[x]<First[y];
}
inline void Get_Rank()
{memset(First,0,sizeof(First)),MAX=n;for(int i=1;i<=n;i++) sa[i]=i;for(int i=1;i<=n;i++) First[i]=s[i],Second[i]=0;sort(sa+1,sa+n+1,cmp);for(int p=1;p<=n;p<<=1){for(int i=1;i<=n;i++) Second[i]=First[i+p];sort(sa+1,sa+n+1,cmp),swap(Second,First),MAX=First[sa[1]]=1;for(int i=2;i<=n;i++)if(Second[sa[i]]!=Second[sa[i-1]] || Second[sa[i]+p]!=Second[sa[i-1]+p])First[sa[i]]=++MAX;else First[sa[i]]=MAX;if(MAX==n) return;}
}

如果要进一步优化复杂度,可以用基数排序代替上面的快速排序,大致步骤如下:

  • 先将 \(n\) 个串的第二关键字排序。即,先将 \(p\) 个空串放在最前面,再按照上一次排完的取。
  • 再将第一关键塞入桶中,对他们排序记录每一个第一关键字对应最终排名的区间,再用已经排序完的第二关键字去对应他们。
  • 最后根据后缀树组重构 \(\text{Rank}\) 数组,即每个字串的排名,记得去重即可。

P3809 【模板】后缀排序

//O(n\log n)
inline void Sort()
{for(int i=1;i<=MAX;i++) cnt[i]=0;for(int i=1;i<=n;i++) cnt[Rank[tmp[i]]]++;for(int i=1;i<=MAX;i++) cnt[i]+=cnt[i-1];for(int i=n;i>=1;i--) sa[cnt[Rank[tmp[i]]]--]=tmp[i],tmp[i]=0;
}
inline void Get_SA()
{for(int i=1;i<=n;i++) Rank[i]=s[i],tmp[i]=i;MAX=255,Sort();for(int p=1;p<=n;p<<=1){int num=0;for(int i=n-p+1;i<=n;i++) tmp[++num]=i;for(int i=1;i<=n;i++) if(sa[i]>p) tmp[++num]=sa[i]-p;Sort(),swap(Rank,tmp),MAX=Rank[sa[1]];for(int i=2;i<=n;i++)if(tmp[sa[i]]!=tmp[sa[i-1]] || tmp[sa[i]+p]!=tmp[sa[i-1]+p])Rank[sa[i]]=++MAX;else Rank[sa[i]]=MAX;if(MAX==n) return;}
}

基础运用

说了这么多,后缀数组到底有什么用呢?

多模式匹配

回忆AC 自动机,它常用于多模式串匹配文本串的问题,复杂度为 \(\mathcal{O(n)}\) 的,相较而言后缀数组就显得比较逊

我们对文本串建立后缀树组,将所有后缀利用后缀树组按照字典序排序。

对于每一个模式串,在所有后缀中二分并 \(\mathcal{O(n)}\) 检查,匹配一个的复杂度是 \(\mathcal{O(m\log n)}\)\(m\) 是模式串长度, \(n\) 是文本穿长度。

最长公共前缀(LCP)

只有一个 \(\text{SA}\) 数组能够完成的事情非常有限,所以通常需要用到两个辅助数组 \(\text{Hight,Rank}\)

\(\text{Rank}\) 表示后缀 \(i\) 在所有后缀中的排名(即上面定义中使用的 \(x\) 数组)。

\(\text{Height}\) 表示 \(\text{SA}_{i-1},\text{SA}_{i}\) 两个子串的 LCP(Longesr Common Prefix)长度。

\(\text{Rank}\) 比较好求,那么 \(\text{Height}\) 怎么求出呢?

有一个非常重要的定理:设 \(h_i=\text{Height}(\text{SA}_i)\),一定有:(不会证明)

\[h_i\ge h_{i-1}-1 \]

有了这个结论就可以快速求出每个 \(\text{Height}\) 了。

两个后缀的 LCP 就是 \(\min\{\text{Height}_k\}(k\in(i,j])\)

inline void Get_Height()
{for(int i=1,j,tmp=0;i<=n;i++){if(Rank[i]==1) { Height[Rank[i]]=0; continue; }if(tmp) tmp--;j=sa[Rank[i]-1];while(i+tmp<=n && j+tmp<=n && s[i+tmp]==s[j+tmp]) tmp++;Height[Rank[i]]=tmp;}
}

例题

SP694 Distinct Substrings

给定一个长度为 \(n(n\le 10^6)\) 的字符串,求出该字符串有多少个本质不同的字串。(虽然原题 \(n\) 只有 \(1000\))

\(\text{Height}\) 数组的含义是排名为 \(i\) 的后缀和排名第 \(i-1\) 的后缀的 LCP 长度,我们发现这个长度正好是以 \(\text{SA}_i\) 开头和以 \(\text{SA}_{i-1}\) 开头的字串算重的数量。所以最终的式子就是:

\[\dfrac{n\times(n+1)}{2}-\sum_{i=2}^{n}\text{Height}_i \]

复杂度 \(\mathcal{O(n\log n)}\)

P2852 [USACO06DEC]Milk Patterns G

给定字符集大小为 \(10^6\) 且长度为 \(n(n\le 2\times 10^4)\) 的字符串 \(s\),给定循环次数 \(k(k\le n)\),找出在字符串中至少循环 \(k\) 次(可以由重叠)的字串的最大长度。

考虑求出字符串的 \(\text{Height}\),那么答案就是所有长度为 \(k\)\(\text{Height}\) 数组子区间中数的最小值中的最大值。

P3181 [HAOI2016]找相同字符

给定两个长度分别不大于 \(2\times 10^5\) 的字符串,求出在两个字符串中各取出一个子串使得这两个子串相同的方案数。两个方案不同当且仅当这两个子串中有一个位置不同。

不会了

\(\bigstar\texttt{Hint}\):考虑从一个串中取出两个子串使得他们相同的方案数,就是这个串所有后缀两两之间的 LCP 长度之和。

那么考虑将两个串拼在一起,当然中间需要加上一个奇怪的字符,计算方案数。

当然这样会算上来自同一个串的情况,所以需要将每个串单独计算后减去贡献。

那么上面的贡献如何计算?其实需要求的是 \(\text{Height}\)所有子区间的区间最小值的和

\(\bigstar\texttt{Trick}\):要计算一个长度不超过 \(10^5\)序列所有子区间的最小值的和,可以用单调栈解决。考虑从前往后加入每一个元素,显然如果有前面的元素大于这个元素,前面的元素的值以后都没用了。维护一个递增的单调栈,并记录这个元素中原本包含的元素个数,同时统计这个单调栈的权值之和,每次累计即可。

\(\bigstar\texttt{Important}\):注意分隔符不要用 \0,可以用 \(z+1\) 等等。

P4070 [SDOI2016]生成魔咒

原本有一个空串,有 \(n\) 次操作,每次向串尾加入一个字符,字符集 \(10^9\),同时询问本质不同字串数量。

\(n\le 10^5\)

\(\bigstar\texttt{Trick}\)倒置字符串!!!

如果我们每次向串首加入一个字符,只新产生了一个后缀,不用考虑对所有后缀的影响。

但是如果我们每次重新维护 \(\text{Rank}\)\(\text{Height}\) 的话,还是会非常难做,所以用 \(\text{Rank}\) 的相对大小维护下面的 \(\text{Rank}\) 比它小、大的后缀。

先对倒置后的整个字符串求一遍 \(\text{Rank}\)\(\text{Height}\),由于我们需要求的是包含新加入字符的子串中,重复出现的字串个数,可以直接和已经加入的后缀中 \(\text{Rank}\) 离它最近的(用 set,除非你想打一个 Splay),并求出重复的长度。

SP1811 LCS - Longest Common Substring

给定两个长度不超过 \(2.5\times 10^5\) 的字符串,求出他们的 LCS。两个字符串的 LCS 是他们的最长公共子串。

好像把两个串用奇怪的字符合起来,再记录每一个 \(\text{Rank}\) 大于它的和它来自不同字符串的位置,求 LCP 长度即可。

P5341 [TJOI2019]甲苯先生和大中锋的字符串

给定长度不超过 \(10^5\) 的字符串和重复次数 \(k\),记 \(cnt_i\) 表示长度 \(s\) 所有长度为 \(i\) 的子串中恰好\(s\) 中出现 \(k\) 次的子串数量。问 \(cnt_i\) 最大的 \(i\)

不会了

\(\bigstar\texttt{Hint}\):考虑 \(R=\min_{i=l}^{l+k-1}{\text{Height}_i}\) 什么时候不能作为答案,记 \(L=\max(\text{Height}_{l-1},\text{Height}_{i+k})\),则一定有当 \(1\le len\le L\) 时,长度为 \(len\) 的子串出现次数大于 \(k\) 次。则可以选择的去长度为 \((L,R]\)

注意一下 \(k=1\) 的特判,其他用单调队列维护即可。

\(\bigstar\texttt{Important}\):注意初始化的时候需要将 \(\text{Height}_{n+1}\) 初始化为 \(0\)!!!

P2463 [SDOI2008] Sandy 的卡片

非常版,一眼差分,答案就是 \(\text{Height}\) 数组中,包含所有 \(n\) 个串的子区间中 \(\text{Height}\) 最小值的最大值。

P4094 [HEOI2016/TJOI2016]字符串

咕咕咕

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

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

相关文章

微软热门开源项目及代码库地址

点击蓝字关注我这几年来&#xff0c;微软在开源与社区方向的努力与成就是全世界有目共睹的。微软的开源项目超过2000多个&#xff0c;挑了一些比较火热的给大家整理了一下。欢迎补充~Visual Studio Code非常流行的跨平台代码编辑器&#xff0c;提供全面的编辑和调试支持、可扩展…

[树链剖分][SDOI 2011]染色,Housewife Wind

文章目录T1&#xff1a;Housewife Wind题目题解codeT2&#xff1a;染色题目题解code今天选择写这篇博客主要是为了告诉大家一个道理&#xff0c;数组比vectorvectorvector快太多了&#xff0c;我这两道题第一次都因为vectorvectorvector&#xff0c;TTT到飞起 T1&#xff1a;…

ASP.NET Core 网站运行时修改设置如何自动生效

点击蓝字关注我在ASP.NET Core中&#xff0c;如果修改了appsettings.json中的设置&#xff0c;那么默认情况下就得重启网站才能生效。有没有办法在修改设置后自动刷新并应用呢&#xff1f;背景首先&#xff0c;我们看看默认模板建出来的 ASP.NET Core 网站&#xff0c;配置文件…

1022. 宠物小精灵之收服

1022. 宠物小精灵之收服 题意&#xff1a; 现在有n个胶囊&#xff0c;m个生命值&#xff0c;k个怪物&#xff0c;每个怪物需要a[i]个胶囊&#xff0c;且会造成b[i]个伤害后才能捕获&#xff0c;问在活着的前提下&#xff0c;最多捕获多少怪物&#xff0c;在怪物最多的情况下剩…

【周末狂欢赛6】[AT1219]历史研究(回滚莫队),大魔法师(矩阵+线段树),单峰排列

文章目录T1&#xff1a;单峰排列题目题解codeT2&#xff1a;历史研究题目题解codeT3&#xff1a;大魔法师题目题解code我可能这辈子都更不出来狂欢赛5了&#xff0c;先咕咕 T1&#xff1a;单峰排列 题目 一个n的全排列A[i]是单峰的&#xff0c;当且仅当存在某个x使得A[1]<…

YBTOJ:圈套问题(分治法、鸽笼原理)

文章目录题目描述数据范围解析代码图片转载自&#xff1a; https://blog.csdn.net/weixin_43346722/article/details/118435430题目描述 平面上有 n个点&#xff0c;用n个大小相同的圆分别将一个点作为圆心&#xff0c;同时满足圆圈不相交&#xff0c;求圆的最大半径。 数据范…

ASP.NET Core 实战:使用 NLog 将日志信息记录到 MongoDB

一、前言在项目开发中&#xff0c;日志系统是系统的一个重要组成模块&#xff0c;通过在程序中记录运行日志、错误日志&#xff0c;可以让我们对于系统的运行情况做到很好的掌控。同时&#xff0c;收集日志不仅仅可以用于诊断排查错误&#xff0c;由于日志同样也是大量的数据&a…

[学习笔记] 伸展树splay详解+全套模板+例题[Luogu P3369 【模板】普通平衡树]

文章目录引入概念全套模板变量声明updaterotate旋转splay操作insert插入delete删除查找x的位置查找第k大前驱/后继极小值-inf和极大值inf的作用例题&#xff1a;P3369 【模板】普通平衡树题目code声明一下&#xff0c;许多代码的注解都在模板代码里面写了的&#xff0c;所以正文…

手写AspNetCore 认证授权代码

在普通的MVC项目中 我们普遍的使用Cookie来作为认证授权方式&#xff0c;使用简单。登录成功后将用户信息写入Cookie&#xff1b;但当我们做WebApi的时候显然Cookie这种方式就有点不适用了。在dotnet core 中 WebApi中目前比较流行的认证授权方式是Jwt (Json Web Token) 技术。…

YBTOJ:采矿战略(线段树维护dp、树链剖分)

文章目录题目描述解析代码题目描述 所谓线段树维护dp&#xff0c;就是在线段树上维护dp &#xff08;逃&#xff09; 解析 把树剖一下后就变成了区间问题 考虑建一棵线段树&#xff0c;每一个结点都是一个背包 这样就能区间查询&#xff0c;也能带修了 这种做法复杂度其实并不…

【用皇宫三十六计生存法则带你走进LCT(动态树)】LCT概念+模板+例题【洛谷P3690 Link Cut Tree(动态树)】

文章目录LCT概念模板rotatoisrootsplayaccessmakerootsplitfindrootlinkcut封装版例题题目code普通版code封装版这篇博客主要是帮助大家理解各个模板及LCTLCTLCT的意思&#xff0c;方便理解&#xff0c;模板写法的理解在代码里有注释详解&#xff0c;如果要看原理的话&#xff…

迈向现代化的 .Net 配置指北

1. 欢呼 .NET Standard 时代我现在已不大提 .Net Core&#xff0c;对于我来说&#xff0c;未来的开发将是基于 .NET Standard&#xff0c;不仅仅是 面向未来 &#xff0c;也是 面向过去&#xff1b;不只是 .Net Core 可以享受便利&#xff0c; .NET Framework 不升级一样能享受…

YBTOJ洛谷P2042:维护数列(平衡树)

文章目录题目描述解析删除区间插入数列修改&翻转区间和&最大子段和代码传送门题目描述 解析 阴间题… 这不是裸的板子吗&#xff1f; 国赛真的有人能把这题写出来吗… 应该算一道练习作用很强的题了 写完这题&#xff0c;各种平衡树维护区间操作的方法可以说是毕业了吧…

CAP 2.4版本发布,支持版本隔离特性

前言自从上次 CAP 2.3 版本发布 以来&#xff0c;已经过去了几个月的时间&#xff0c;这几个月比较忙&#xff0c;所以也没有怎么写博客&#xff0c;趁着2019年到来之际&#xff08;现在应该是2019年开始的时候&#xff09;&#xff0c;CAP也发布了2018年的最后一个大版本 2.4&…

【周末狂欢赛7】【NOIP模拟赛】七夕祭,齿轮(dfs),天才黑客

文章目录T1题目题解codeT2题目题解codeT3题目题解codeT1 题目 七夕节因牛郎织女的传说而被扣上了「情人节」的帽子。于是TYVJ今年举办了一次线下七夕祭。Vani同学今年成功邀请到了cl同学陪他来共度七夕&#xff0c;于是他们决定去TYVJ七夕祭游玩。 TYVJ七夕祭和11区的夏祭的…

.NET Core 如何为项目提供高性能解决方案?

本系列&#xff0c;我们将探讨.NET Core 的一些好处&#xff0c;以及它如何为市场提供高性能解决方案&#xff0c;为传统.NET 开发人员和技术人员提供帮助。正文前言随着.NET Core 2.0 在 2016 年首次发布&#xff0c;微软拥有了这个通用、模块化、跨平台开源项目的下一个主要版…

[2.9训练]【CF909C】Python Indentation,【CF909D】Colorful Points,【CF909E】Coprocessor

文章目录T1&#xff1a;Python Indentation题目题解codeT2&#xff1a;Colorful Points题目题解codeT3&#xff1a;Coprocessor题目题解codeT1&#xff1a;Python Indentation 题目 题目描述 In Python, code blocks don’t have explicit begin/end or curly braces to mark…

Docker最全教程之使用Tencent Hub来完成CI(十)

本周更新两篇&#xff0c;保证不太监&#xff01;在本系列教程中&#xff0c;笔者希望将必要的知识点围绕理论、流程&#xff08;工作流程&#xff09;、方法、实践来进行讲解&#xff0c;而不是单纯的为讲解知识点而进行讲解。也就是说&#xff0c;笔者希望能够让大家将理论、…

[2.7]【CF933A】A Twisty Movement【CF926B】Add Points【CF917A】The Monster【CF919E】Congruence Equation

文章目录T1&#xff1a;A Twisty Movement题目题解codeT2&#xff1a;Add Points题目题解codeT3&#xff1a;The Monster题目题解codeT4&#xff1a;Congruence Equation题目题解codeT1&#xff1a;A Twisty Movement 题目 题目 题解 因为aia_iai​1/21/21/2&#xff0c;于…

LIS最长上升子序列

LIS算是比较经典的问题&#xff0c;常用的是O(n^2)的方法 for(int i1;i<n;i){dp[i]1;for(int j1;j<i;j){if(a[j]<a[i])dp[i]max(dp[i],dp[j]1);}mxmax(mx,dp[i]);}我们这里优化成O(nlogn) 我们模拟一个栈stack&#xff0c;每读入一个数&#xff0c;如果这个数大于栈顶…