上一次做 SA / SAM 相关的题还要数到某场毒瘤 NOIP 模拟赛……这么久没做了都快忘光了……写点东西记录一些最近做到的好题。

LOJ2059 「TJOI / HEOI2016」字符串

题意

给定一个长度为 \(n\) 的字符串 \(s\) ,接下来有 \(m\) 次询问。每次询问给出四个参数 \(a,b,c,d\) 。求 \(s[a,b]\) 的所有子串和 \(s[c,d]\) 的 LCP 的最大值。

\(n,m \le 10^5\) 。

题解

题目可以转化为,求一段连续的后缀与 \(s[c,d]\) 的 LCP 的最大值。由于这个最大值对位置还有一个限制,因此直接求不大好做,考虑二分答案转化为判定问题。

二分答案之后,就只需要查询,和 \(s[c,d]\) 的 LCP \(\ge k\) 的后缀是否在一段区间内出现过。这样只要把 SA 建出来,就只需要查询一个区间内是否出现了某个区间内的数,直接按照 \(rk\) 建一棵主席树,就可以在 \(O(n \log^2 n)\) 的时间内解决了。

SA + 主席树也是一个相当常见的套路,可以注意一下。另外 SAM 做这个题复杂度似乎没有优化?所以也没啥意义了。

UVA10829 L-Gap Substrings

题意

给定一个串 \(S\) 。求有多少 \(S\) 的子串是形如 \(UVU\) 的形式,且 \(U\) 不是空串,\(V\) 中恰好包含了 \(g\) 个字符。

数据组数 \(T \le 10,|S| \le 5 \times 10^4,g \le 10\) 。

题解

考虑枚举 \(U\) 串的长度。这样我只要每隔 \(|U|\) 个放一个关键点。然后对于两两相邻的关键点,将后面的关键点强行往后移 \(|V|\) 的长度,检查以这两个关键点为结尾的串的 \(\mathrm{LCS}\) 以及以这两个关键点开头的串的 \(\mathrm{LCP}\),就可以快速统计对于某个串长的答案。这样利用 \(\mathrm{SA}\) 就可以快速实现这个东西了,复杂度是 \(O(n \log n)\) 的。

和刚刚那个题一样,先枚举产生贡献的串长,然后每隔固定长度放一个关键点。每次检查关键点的信息也是一个常见的套路。类似的题还有「NOI2016」优秀的拆分。

Codeforces 700E Cool Slogans

题意

给出一个长度为 \(n\) 的字符串 \(s_1\)。定义一个字符串序列 \(s_{1 \sim k}\) ,满足性质:\(s_i\) 在 \(s_{i - 1} (i \ge 2)\) 中出现至少两次,问最大的 \(k\) 是多少,使得从 \(s_1\) 开始到 \(s_k\) 都满足这样一个性质。

\(n \le 2 \times 10^5\) 。

题解

首先建出 \(\mathrm{SAM}\)。于是我们只要从 \(\mathrm{SAM}\) 的 \(\mathrm{fail}\) 树上从根节点往下的一条路径中选出尽量多的节点,满足上一个点所代表的子串在这一个点至少出现了 \(2\) 次。由于一个点所代表的若干个集合的 \(endpos\) 集合是相同的,因此我们可以直接取这个点代表的所有子串中,长度最长的子串。

考虑从上往下不断贪心选点,那么这一个点能否被选择,只取决于这一个点代表的最长子串,是否包含了上一个被选择的点所代表的子串至少 \(2\) 次。这里可以考虑用线段树合并,来维护 \(endpos\) 集合。于是我需要对 \(\mathrm{SAM}\) 上每个点多记录一个第一次扩展出当前节点的时间 \(id_i\)。这样就可以得到,这个节点所代表的字符串,对应原字符串的 \([id_i - len_i + 1,id_i]\) 这样一个区间。假如我要查询节点 \(u\) 所代表的最长字符串在节点 \(v\) 代表的最长字符串出现了多少次,那么我只需要在 \(u\) 这个节点的线段树上查询 \(endpos\) 位于 \([id_v - len_v + len_u,id_i]\) 这个区间内的和即可。

LOJ 2720 「NOI2018」你的名字

题意

给定一个模板串 \(S\) 。接下来会给出 \(m\) 个询问。每次询问给出询问串 \(T\) 和区间 \([l,r]\)。求 \(T\) 串有多少个本质不同的子串没有在 \(S\) 串的 \([l,r]\) 中出现过。

\(|S| \le 5 \times 10^5,m \le 10^5,\sum|T| \le 10^6\) 。

题解

考虑枚举 \(T\) 串的每一个前缀 \(T_{1,i}\),求出这个前缀与 \(S_{l,r}\) 这个串的每个后缀的 \(\text{LCP}\) 的 \(\max\),记为 \(pre_i\)。这样算答案的时候,我只需要枚举 \(T\) 串的 \(\mathrm{SAM}\) 上每一个节点,这个节点对于答案的贡献即为 \(len_i - max(len_{link_i},pre_{id_i})\)。其中 \(id_i\) 同样表示 \(i\) 节点第一次扩展出来的时间。

如何求 \(pre_i\) 呢?这个同样可以通过建出 \(S\) 串的 \(\mathrm{SAM}\) ,然后用线段树合并维护 \(endpos\) 集合。按照顺序枚举每个前缀,从 \(pre_{i - 1} + 1\) 开始尝试,记录包含当前这个长度的后缀在 \(S\) 串的 \(\mathrm{SAM}\) 上深度最浅的点的位置 \(u\),并且在线段树上查询以 \(u\) 为根的线段树上 \(endpos\) 位于 \([l + t,r]\) 这个区间内的点是否存在,其中 \(t\) 为当前尝试的长度。如果匹配失败,就减少这个匹配的长度并再次尝试,直到匹配成功或者匹配长度减少为 \(0\) 则退出。时间复杂度是 \(O((|S| + |T|) \log n)\) 的。

代码中最后统计贡献的时候对 \(0\) 取了 \(\max\) ,而且这个 \(\max\) 不取还会有问题,来解释一下原因。事实上克隆节点就相当于把某个节点的 \(\text{fail}\) 拆出来了,这样克隆节点的 \(len\) 就会小于其扩展出来的时间,而 \(pre_i\) 上界是 \(i\) ,因此可能会被减到负数。

LOJ 6041 事情的相似度

题意

给定一个长度为 \(n\) 的 \(01\) 串,并定义第 \(i\) 个前缀,表示从第 \(1\) 个字符到第 \(i\) 个字符组成的字符串。接下来有 \(m\) 次询问。每次询问会给出一个区间 \([l,r]\) ,查询第 \(l\) 个前缀到第 \(r\) 个前缀中,\(\mathrm{LCP}\) 最大的一对前缀的 \(\mathrm{LCP}\)。

\(n,m \le 10^5\) 。

题解

考虑建出这个串的 \(\mathrm{SAM}\),这样问题转化为,每次询问一个区间内的点中,所有点对的 \(\mathrm{LCA}\) 的 \(len\) 的最大值。注意到询问是可以离线的,因此考虑把询问按照右端点排序。

考虑如果我们维护出了每个节点的子树内出现时间最晚的点,姑且称作这个点的颜色,那么新加入一个点,从这个点到根节点的路径上,会经过若干段相同颜色的点,那么我只要在每一段深度最深的点处,在树状数组上修改一下。这个和 \(\mathrm{LCT}\) 的 \(access\) 操作是一样的,可以方便地用 \(\mathrm{LCT}\) 维护。查询的时候只需要在树状数组上查询即可。

当然直接在线也是有做法的。考虑用 \(\texttt{std :: set}\) 启发式合并维护 \(endpos\) 集合。每次新加一个数,产生贡献的肯定只会是这个数在 \(\texttt{set}\) 上的前驱后继。原因是,当前合并到这个区间,那么 \(\mathrm{LCA}\) 的 \(maxlen\) 是固定的,这样产生贡献的点对编号差距尽可能小,才可能对更多的询问贡献。这样维护完之后,只需要再做一次二维数点,统计一个区域内的最小值即可。树状数组维护 \(\max\) 的时候好像不太好删除,可以考虑把其中一维反过来,查询两维均小于等于某一个数的区域即可。

两种做法的复杂度都是 \(O(n \log^2 n)\) 的。

SA / SAM 题目集的更多相关文章

  1. Java高级程序员(5年左右)面试的题目集

    Java高级程序员(5年左右)面试的题目集 https://blog.csdn.net/fangqun663775/article/details/73614850?utm_source=blogxg ...

  2. 浙大版《C语言程序设计(第3版)》题目集 --总结

    浙大版<C语言程序设计(第3版)>题目集 此篇博客意义为总结pta上浙大版<C语言程序设计(第3版)>题目集所做题目的错误点,心得体会. 1.练习2-10 计算分段函数[1] ...

  3. KMP,Trie,AC自动机题目集

    字符串算法并不多,KMP,trie,AC自动机就是其中几个最经典的.字符串的题目灵活多变也有许多套路,需要多做题才能体会.这里收集了许多前辈的题目做个集合,方便自己回忆. KMP题目:https:// ...

  4. PTA数据结构与算法题目集(中文) 7-43字符串关键字的散列映射 (25 分)

    PTA数据结构与算法题目集(中文)  7-43字符串关键字的散列映射 (25 分) 7-43 字符串关键字的散列映射 (25 分)   给定一系列由大写英文字母组成的字符串关键字和素数P,用移位法定义 ...

  5. PTA数据结构与算法题目集(中文) 7-42整型关键字的散列映射 (25 分)

    PTA数据结构与算法题目集(中文)  7-42整型关键字的散列映射 (25 分) 7-42 整型关键字的散列映射 (25 分)   给定一系列整型关键字和素数P,用除留余数法定义的散列函数将关键字映射 ...

  6. PTA数据结构与算法题目集(中文) 7-41PAT排名汇总 (25 分)

    PTA数据结构与算法题目集(中文)  7-41PAT排名汇总 (25 分) 7-41 PAT排名汇总 (25 分)   计算机程序设计能力考试(Programming Ability Test,简称P ...

  7. PTA数据结构与算法题目集(中文) 7-40奥运排行榜 (25 分)

    PTA数据结构与算法题目集(中文)  7-40奥运排行榜 (25 分) 7-40 奥运排行榜 (25 分)   每年奥运会各大媒体都会公布一个排行榜,但是细心的读者发现,不同国家的排行榜略有不同.比如 ...

  8. PTA数据结构与算法题目集(中文) 7-39魔法优惠券 (25 分)

    PTA数据结构与算法题目集(中文)  7-39魔法优惠券 (25 分) 7-39 魔法优惠券 (25 分)   在火星上有个魔法商店,提供魔法优惠券.每个优惠劵上印有一个整数面值K,表示若你在购买某商 ...

  9. PTA数据结构与算法题目集(中文) 7-38寻找大富翁 (25 分)

    PTA数据结构与算法题目集(中文)  7-38寻找大富翁 (25 分) 7-38 寻找大富翁 (25 分)   胡润研究院的调查显示,截至2017年底,中国个人资产超过1亿元的高净值人群达15万人.假 ...

随机推荐

  1. 在Git中添加一个项目

    首先保证Git服务器正确配置,管理员机器可正常连接并使用Git. 第一步:在服务器上新建一个项目仓库 切换到git用户: a@ubuntu:/home/git$ su - git $ cd /home ...

  2. I/O中断处理详细过程

    1.CPU发送启动I/O设备的命令,将I/O接口中的B触发器置1,D触发器置O. 2.设备开始工作,需要向CPU传送数据时,将数据送入数据缓冲器中. 3.输入设备向I/O接口发出“设备工作结束”的信号 ...

  3. 从零开始搭建VUE项目

    前言: 此样板面向大型,严肃的项目,并假定您对Webpack和vue-loader有些熟悉. 请务必阅读vue-loader的常见工作流配方的文档. 如果您只想尝试vue-loader或者鞭打一个快速 ...

  4. setState的参数接收函数

  5. react中如何使用动画效果

    在react中想要加入动画效果 需要引入 import {CSSTransitionGroup} from 'react-transition-group' //加入react 动画包 import ...

  6. jvm 虚拟机内存模型

    来源:https://blog.csdn.net/A_zhenzhen/article/details/77917991?locationNum=8&fps=1    https://blog ...

  7. 【学亮IT手记】jQuery each()函数用法实例

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script sr ...

  8. MySQL5.5 安装配置方法教程

    MySQL下载地址:http://dev.mysql.com/downloads/installer/ 1.首先进入的是安装引导界面 2.然后进入的是类型选择界面,这里有3个类型:Typical(典型 ...

  9. MyBatis映射文件1(增删改、insert获取自增主键值)

    增删改 Mybatis为我们提供了<insert>.<update>.<delete>标签来对应增删改操作 在接口中写增删改的抽象方法 void addEmp(Em ...

  10. 优化CSS重排重绘与浏览器性能

    关于CSS重排和重绘的概念,最近看到不少这方面的文章,觉得挺有用,在制作中考虑浏览器的性能,减少重排能够节省浏览器对其子元素及父类元素的重新渲染:避免过分的重绘也能节省浏览器性能:优化动画,使用3D启 ...