上一次做 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)\) 的。

转载于:https://www.cnblogs.com/ShichengXiao/p/10245374.html

SA / SAM 题目集相关推荐

  1. PTA数据结构与算法题目集6-4 6-3 6-8

    PTA数据结构与算法题目集(中文) 6-4 链式表的按序号查找 ElementType FindKth( List L, int K ){int index = 0;while(L){++index; ...

  2. PTA数据结构与算法题目集 6-9 二叉树的遍历

    PTA数据结构与算法题目集(中文) 6-9 二叉树的遍历 void InorderTraversal( BinTree BT ){if(BT==NULL)return;if(BT->Left){ ...

  3. PTA 基础编程题目集 6-6 求单链表结点的阶乘和

    PTA 基础编程题目集 6-6 求单链表结点的阶乘和 本题要求实现一个函数,求单链表L结点的阶乘和.这里默认所有结点的值非负,且题目保证结果在int范围内. 函数接口定义: int Factorial ...

  4. PTA 基础编程题目集 7-27 冒泡法排序 C语言

    PTA 基础编程题目集 7-27 冒泡法排序 C语言 将N个整数按从小到大排序的冒泡排序法是这样工作的:从头到尾比较相邻两个元素,如果前面的元素大于其紧随的后面元素,则交换它们.通过一遍扫描,则最后一 ...

  5. PTA 基础编程题目集 7-33 有理数加法 C语言

    PTA 基础编程题目集 7-33 有理数加法 C语言 本题要求编写程序,计算两个有理数的和. 输入格式: 输入在一行中按照a1/b1 a2/b2的格式给出两个分数形式的有理数,其中分子和分母全是整形范 ...

  6. PTA 基础编程题目集 7-24 约分最简分式 C语言

    PTA 基础编程题目集 7-24 约分最简分式 C语言 分数可以表示为分子/分母的形式.编写一个程序,要求用户输入一个分数,然后将其约分为最简分式.最简分式是指分子和分母不具有可以约分的成分了.如6/ ...

  7. PTA 基础编程题目集 7-7 12-24小时制 C语言

    PTA 基础编程题目集 7-7 12-24小时制 C语言 编写一个程序,要求用户输入24小时制的时间,然后显示12小时制的时间. 输入格式: 输入在一行中给出带有中间的:符号(半角的冒号)的24小时制 ...

  8. PTA 基础编程题目集 7-22 龟兔赛跑 C语言

    PTA 基础编程题目集 7-22 龟兔赛跑 C语言 乌龟与兔子进行赛跑,跑场是一个矩型跑道,跑道边可以随地进行休息.乌龟每分钟可以前进3米,兔子每分钟前进9米:兔子嫌乌龟跑得慢,觉得肯定能跑赢乌龟,于 ...

  9. PTA 基础编程题目集 7-21 求特殊方程的正整数解 C语言

    PTA 基础编程题目集 7-21 求特殊方程的正整数解 C语言 输入样例1: 884 输出样例1: 10 28 20 22 输入样例2: 11 输出样例2: No Solution #include& ...

  10. PTA 基础编程题目集 7-20 打印九九口诀表 C语言

    PTA 基础编程题目集 7-20 打印九九口诀表 C语言 下面是一个完整的下三角九九口诀表: 本题要求对任意给定的一位正整数N,输出从11到NN的部分口诀表. 输入格式: 输入在一行中给出一个正整数N ...

最新文章

  1. java注释@para_Java中文档注释各字段的含义是什么?例如author表示作者,para表示参数等...
  2. 2021年春季学期-信号与系统-第十二次作业参考答案-第五小题
  3. 从淘特升级,看电商特别模式的特别价值
  4. fortify安装_Rjava的安装
  5. (*长期更新)软考网络工程师学习笔记——Section 16 磁盘存储技术和网络规划设计
  6. 如何将前端数据保存到文件
  7. LeetCode 933. 最近的请求次数(queue)
  8. 罗彻斯特大学计算机科学系专业排名,罗切斯特大学计算机专业怎么样?
  9. solr mysql 速度_提高solr的搜索速度
  10. Python处理Excel文档之openpyxl (三)简单的使用
  11. 使用IConfigurationSectionHandler在web.config中增加自定义配置
  12. android利用线程池高效实现异步任务
  13. Mysql mysqld_safe启动与myslqd启动坑
  14. linux r语言 安装包下载,R语言安装程序包(示例代码)
  15. POI的word表格居中
  16. 计算机共享找不到网络连接失败,局域网电脑无法访问共享文件网络共享失败如何解决...
  17. 《影响力》读书总结(一):影响力的武器
  18. android大作业报告总结,android大作业总结报告.doc
  19. im即时通讯开发:浅析MQTT通信协议
  20. HBase CURD之Put

热门文章

  1. 游戏开发之C++内联函数--不受程序员控制的一个函数(C++基础)
  2. Security+ 学习笔记18 密码分析攻击
  3. OSPFv3中LSA详解(七)——Type4类LSA详解
  4. 华三 h3c vrrp和监视端口配置
  5. leetcode 13 13. 罗马数字转整数 (python)
  6. 通过汉诺塔深入理解递归流程。
  7. python yield,到这个层次,才能叫深入哈
  8. kotlin支持jdk1.8编译,使用Java8特性
  9. 从分析性数据库ADS中导出数据
  10. 高性能计算多集群管理平台