一些SAM的 基础 题目。(主要是我不想写SAM的原理啊啊啊)

有的题目是SA的思维题,但是可以用SAM平推,基本上可以不动脑子。

除非有特殊说明,否则将字符集看作所有小写字母,构造SAM复杂度记为 \(O(|S|)\)

基础SAM

Luogu【模板】后缀自动机 (SAM)

求 \(S\) 串中出现次数不为 \(1\) 的子串的出现次数乘上子串长度的最大值。

对于某一个等价类对应的节点 \(v\) ,如果 \(|endpos(v)|\neq 1\),它可能会对答案做出 \(len(v)\times |endpos(v)|\) 的贡献。而 \(|endpos(v)|\) 等于 \(link\) 树上子树内为 \(S\) 串节点的个数。

总体复杂度 \(O(|S|)\) 。

Luogu P2408 不同子串个数

求 \(S\) 串的不同子串个数。

等价于 SAM 上路径数,DP求值即可。

总体复杂度 \(O(|S|)\) 。

SPOJ LCS-Longest Common Substring

求串 \(S\) 和 \(T\) 的最长公共字串。

等价于求 \(T\) 前缀中在 \(S\) 的 SAM 上出现的最长后缀的最大值。

考虑在 \(S\) 的 SAM 上跑 \(T\) 串,假设当前在点 \(v\) ,最长后缀长度为 \(l\) ,加入字符 \(t_i\) 。

如果 \(v\) 存在 \(t_i\) 对应转移, \(v\leftarrow nxt_v(t_i),l\leftarrow l+1\) 即可,

否则 \(v\leftarrow link(v),l\leftarrow len(link(v))\) ,及将 \(v\) 跳向 \(link\) 边,直至跳至 \(-1\) 或存在 \(t_i\) 的转移为止。

由于 \(l\) 只会变大 \(|T|\) 次,变小 \(O(|T|)\) 次,所以总体复杂度为 \(O(|S|+|T|)\) 。

[SDOI2016]生成魔咒

动态求不同字串个数。

不同字串个数也等价于 \(\sum len(v)-len(link(v))\) ,每当加入一个新节点的时候,改变 \(link(v)\) 的节点数是 \(O(1)\) 的,可以直接暴力维护变化量。

总体复杂度 \(O(n\log V)\)

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

求 \(S\) 串中出现 \(k\) 次的子串中出现次数最多的长度。

可以 \(O(|S|)\) 时间内建出 SAM 并求出每个节点对应的 \(|endpos(v)|\) ,其中 \(|endpos(v)|=k\) 的会对 \([len(link(v))+1,len(v)]\) 做出 \(1\) 的贡献,线段树维护即可。

单次复杂度为 \(O(|S|log|S|)\) 。

CF802I Fake News (hard)

求出每个子串在 \(S\) 中出现次数的平方和。

对于每个 \(v\) 对应的出现次数和子串个数可知,暴力求和即可。

单次复杂度为 \(O(|S|)\) 。

[TJOI2015]弦论

求出 \(S\) 中第 \(k\) 小的子串。

每一个字串对应 \(SAM\) 上的一条路径,先 DP 一边然后确定在每一个节点的走向即可。

总体复杂度为 \(O(|S|)\) 。

[BJOI2020] 封印

给定 \(S,T,l_i,r_i\) ,求 \(S[l_i\dots r_i]\) 和 \(T\) 的最长公共子串。

先建出 \(T\) 的 SAM ,对于 \(S\) 的每一个前缀,求出最长的是 \(T\) 子串的后缀 \(a_i\) 。

最终答案相当于是 \(\max\limits_{i=l}^r(\min(a_i,i-l+1))\) 。

将所有询问离线下来,用线段树维护答案即可。

总体复杂度为 \(O(|T|+|S|\log|S|+Q\log |S|)\) 。

CF235C Cyclical Quest

给定串 \(S\) ,询问 \(T\) 的所有循环同构在 \(S\) 中的出现次数之和。

考虑将 \(T\) 变成 \(TT\) ,题目就等价于询问 \(TT\) 中所有不同的长度为 \(|T|\) 的子串在 \(S\) 中的出现次数之和。

对 \(S\) 建 SAM ,维护 \(TT\) 在 \(S\) 中的匹配,可以一直跳 \(link\) 跳到 \(len(link(v))\leqslant |T|\) 为止,由于需要不同的串,所以SAM中的每一个点只会对答案做一次贡献。

总体复杂度 \(O(|S|+\sum|T|)\) 。

CF427D Match & Catch

问 \(S\) 和 \(T\) 中均只出现过一次的串的长度的最小值。

考虑建出 \(S\) 串的 SAM ,让 \(T\) 在上面跑匹配。

与一般匹配不同的是,我们还需要维护 \(v\) 的所有 \(link\) 树上祖先,都记录下长度为多少的串出现了多少次。

但由于这个值是单调的,所以只需要维护出经过它的最大的两个 \(\min(len(v),l)\) 即可。

最后在所有 \(|endpos(v)|=1\) 的点中找答案。

总体复杂度 \(O(|S|^2)\) 。

[HAOI2016] 找相同字符

给定两个字符串 \(S_1\) 和 \(S_2\),问有多少个对不同的 \(S_1\) 的子串和 \(S_2\) 的子串相等。

考虑某一个字符串的贡献,等于 \(在S_1中的出现次数\times 在S_2中的出现次数\)。

对应到 \(S_1|S_2\) 上,找到该字符串对应节点,贡献即为 \(|endpos\cap S_1|\times|endpos\cap S_2|\)。

所以,最终答案为 \(\sum (len(v)-len(link(v)))|endpos(v)\cap S_1|\times|endpos(v)\cap S_2|\)。

总体复杂度 \(O(|S_1|+|S_2|)\)。

[CTSC2012]熟悉的文章

给定 \(m\) 个 \(01\) 串 \(T\) ,询问 \(n\) 个串 \(S\) ,问满足存在一种 \(S\) 拆分方式,使得长度大于 \(L\) 的是 \(m\) 中某一个串的子串的串的总长度 \(\geqslant 0.9|S|\) 。

考虑将所有的串 \(T\) 拼在一起,在间隔处插入一个标识符,记为 \(T_0\) ,这样可以对所有 \(T\) 一起建 SAM 保证复杂度。

找到 \(S\) 中每一个前缀在 \(T_0\) 的最长后缀匹配,记为 \(a_i\) ,考虑二分答案求解 \(L\) 。

使用 DP 维护单次转移:记 \(f_i\) 表示前 \(i\) 位,与 \(T_0\) 匹配长度的最大值。

则 \(f_i=\max(f_{i-1},\max\limits_{i-a_i\leqslant j\leqslant i-L}(f_j+i-j))=\max(f_{i-1},\max\limits_{i-a_i\leqslant j\leqslant i-L}(f_j-j)+i)\) 。

由于 \(i-a_i\) 与 \(i-L\) 都是不减的,可以使用单调队列维护转移。

整体复杂度 \(O(\sum|T|+\sum|S|\log |S|)\) 。

CF653F Paper task

给定一个长为 \(n\) 的由'('和')'组成的字符串,问其有多少个本质不同的子串是合法的括号串。

考虑建出SAM,一个字符串的贡献我们在它对于 \(endpos\) 集合中最小的位置计算,则所有的本质不同字符串对应 \(O(n)\) 个形如 \(S[l,r],l\in [a,b]\) 的区间。

使用动态开点线段树维护以每一个位置为结尾有那些起点对应合法的括号串,直接区间查询即可。

时间复杂度 \(O(n\log n)\)。

SAM+可持久化线段树合并

有的时候我们需要在线快速查询 SAM 上某一个节点的 \(endpos\) 集合,同时我们可以通过 \(link\) 树来得到 \(endpos\) 的信息,所以考虑使用可持久化线段树合并来实现。

给国的字符串

询问 \(Q\) 个串 \(T_i\) ,在 \(S\) 中可以找到多少个互不相交的子串 \(T_i\) 。

记 \(n=|S|,m=\sum|T_i|\) 。

如果已经知道了 \(T_i\) 作为 \(S\) 的字串出现的所有位置,不难证明贪心的从左到右选可以选的是最优的策略。

尝试维护这个贪心的过程,我们可以通过在 SAM 上维护每一个节点的 \(endpos\) 来实现 \(ans\log n\) 的查询。

但是,我们发现对于 \(ans\) 极大,如 aaaaa...aa 出现的次数,会被卡到 TLE 。

考虑根号分治: 对于 \(|T_i|>B\) ,必然存在 \(ans<\frac{n}{B}\) ,暴力求解的最劣复杂度为 \(O(\frac{n}{B}\log n)\) ;对于 \(|T_i|<B\) ,我们考虑预处理出来所有的长度 \(\leqslant B\) 的 \(S\) 的字串,做到在 SAM 中匹配到对应节点之后 \(O(1)\) 查询。

由于一次暴力维护是 \(ans\log n\) 的,而 \(S\) 中长度 \(\leqslant B\) 的串只有 \(n+(n-1)+\dots+(n+1-B)=nB-\dfrac{B(B-1)}{2}\) 个,所以暴力维护的 \(\sum ans<nB\) ,暴力维护答案复杂度小于 \(nB\log n\) 。

由于 \(\sum |T_i|=m\) ,所以长度大于 \(B\) 的串只会出现至多 \(O(\frac{m}{B})\) 个,所以最终复杂度为 \(O(nB\log n+\dfrac{m}{B}\times \frac{n}{B}\log n)=O(nB\log n+\dfrac{mn}{B^2}\log n)\) 。

当 \(B=O(\sqrt[3]{m})\) 时,复杂度取到最优 \(O(n\sqrt[3]m\log n)\) 。

[NOI2018] 你的名字

给定字符串 \(S\) , \(Q\) 次询问,求 \(T\) 从有多少个子串是 \(S[l,r]\) 中没有出现的( \(S[l,r]\) 表示 \(S\) 串中第 \(l\) 位到第 \(r\) 位)。

首先考虑 \(l=1,r=n\) ,也就是 \(S[l,r]=S\) 时如何处理:

让 \(T\) 串在 \(S\) 串的 SAM 上跑一次匹配,找到对应每一个前缀的最大后缀匹配是多少。

然后再在 \(T\) 串的 SAM 上处理出每一个节点对应 \(endpos\) 的贡献,复杂度为 \(O(|S|+|T|)\) 。

但是考虑到我们匹配的可能是 \(S\) 的某一部分,所以我们考虑如何在匹配的过程中判断当前节点的 \(endpos\) 是否存在在 \([l,r]\) 中的点,同时它的匹配长度是不是最优的(因为会出现被在 \(l\) 处砍掉一刀的情况)。

考虑用可持久化线段树合并维护 \(endpos\) ,在完成了正常匹配之后,查询 \(endpos\cap[1,r]\) 中最大的点 \(g\) ,如果 \(g\) 不存在或 \(g<l\) ,则需要跳到 \(link(v)\) ,重复此操作。

也有一种可能,就是 \(\min(p,g-l+1)<len(link(v))\) ,其中 \(p\) 为最长匹配长度,这中情况下选择 \(v\) 不优于选择 \(link(v)\) ,所以也要跳到 \(link(v)\) 。

总体复杂度 \(O(|S|+\sum|T|\log|S|)\) 。

CF1037H Security

\(Q\) 次询问,询问 \(S[l,r]\) 中字典序大于 \(T\) 的字典序最小的串。

考虑建出 \(S\) 的 SAM ,同时使用可持久化线段树合并维护 \(endpos\) 。

将 \(T\) 在 \(S\) 中进行匹配,维护每一次对应匹配的字符在 \(S\) 中的位置,初始为 \(l-1\) ,每次找到 \([las+1,r]\) 中最小的 \(endpos\) 。

如果没有,则表示 \(T\) 无法匹配、

找到 \(T\) 在 \(S[l,r]\) 的最长前缀之后,从后往前一次枚举,知道找到某一位的存在一个大于 \(T\) 的串,弹出并输出答案。

总体复杂度 \(O(|S|+26\sum|T|\log|S|)\) ,但是显然跑不满 \(26\) 这个常数。

[HEOI2016/TJOI2016]字符串

给定字符串 \(S\),\(Q\) 次询问,求最长的 \(len\) 使得 \(S[c\dots d]\) 中长为 \(len\) 的后缀是 \(S[a\dots b]\) 的子串。

首先将 \(S\) 串反转,将前缀变成后缀。

由于答案是可二分的,考虑二分答案:

我们通过 \(link\) 树上倍增找到 \(S[d-len+1\dots d]\) 在 SAM 中的位置,查找是否存在在 \([a+len-1,b]\) 的 \(endpos\) 即可。

总体复杂度 \(O(|S|\log^2|S|)\)。

CF666E Forensic Examination

给定字符串 \(S\),和一个些字符串 \(T_1,T_2\dots T-m\),\(Q\) 次询问,问在 \(T_L T_{L+1}\dots T_R\) 中,\(S[l,r]\) 出现次数最多的串中编号最小是哪一个,并输出出现次数。

考虑建出 \(S|T_1|T_2|\dots|T_m\) 的 SAM,其中 '|' 为分隔符。

使用可持久化线段树合并维护每个点的 \(endpos\) 中位置对应第 \(T_j,j=1,2\dots m\) 个串的分别有多少个。

查询 \(S[l,r]\) 的出现次数,考虑从 \(S[1,r]\) 对应的节点进行 \(link\) 树上倍增找到对应节点,在线段树上区间取值即可。

总体复杂度 \(O(|S|+\sum|T|\log(\sum|T|)+Q\log(|S|+\sum|T|))\)。

广义SAM

有的时候我们需要几个串的子串信息,所以考虑将若干个串建到同一个 SAM 中,考虑到 SAM 中的 nxt 数组就是维护了一个 Trie 树,所以考虑在建立 Trie 树的过程中建立 SAM ,由于 SAM 中会所用到之前的节点,所以考虑在 Trie 树上广搜来实现。

Luogu【模板】 广义后缀自动机(广义 SAM)

询问若干个串 \(S_1,S_2\dots S_n\) 中一共有多少个本质不同的子串。

就是基本的建立 SAM 的过程,最后求出 \(\sum len(v)-len(link(v))\) 即可。

总体复杂度 \(O(\sum|S|)\)。

CF452E Three strings

有三个串 \(A,B,C\),对于每一个 \(l\in [1,\min(|A|,|B|,|C|)]\),问有多少对 \((a,b,c)\),满足 \(A[a,a+l-1]=B[b,b+l-1]=C[c,c+l-1]\),答案对 \(10^9+7\) 取模。

建出广义SAM,维护每一个节点对于 \(A,B,C\) 的 \(endpos\) 大小,那么它会对于 \(len(link(v))+1\) 到 \(len(v)\) 作三个集合大小乘积的贡献,差分维护即可。

总体复杂度 \(O(|A|+|B|+|C|)\)。

CF204E Little Elephant and Strings

有 \(n\) 个字符串,对于每一个串,询问其有多少个子串在至少 \(k\) 个串中出现过。

建出广义SAM,使用动态开点线段树维护出 \(endpos\) 集合在每一个串中有多少个,如果这个 \(endpos\) 中有 \(\geqslant k\) 个串,则令这些数均做 \(len(v)-len(link(v))\) 的贡献。

在 \(link\) 树上跑线段树合并即可得到答案。

总体复杂度 \(O(\sum |S|\log n)\)

SAM题目合集的更多相关文章

  1. array题目合集

    414. Third Maximum Number 给一个非空的整数数组,找到这个数组中第三大的值,如果不存在,那么返回最大的值.要求时间复杂度为o(n) 例如: Example 1: Input: ...

  2. codeforces题目合集(持续更新中)

    CF280CCF280CCF280C 期望dp CF364DCF364DCF364D 随机化算法 CF438DCF438DCF438D 线段树 CF948CCF948CCF948C 堆 CF961EC ...

  3. 前端面试经典题目合集(HTML+CSS)一

    1.说说你对HTML语义化的理解? (1)什么是HTML语义化? 根据内容的结构化(内容语义化),选择合适的标签(代码语义化)便于开发者阅读和写出更优雅的代码的同时让浏览器的爬虫和机器很好地解析. ( ...

  4. 区间DP入门题目合集

      区间DP主要思想是先在小区间取得最优解,然后小区间合并时更新大区间的最优解.       基本代码: //mst(dp,0) 初始化DP数组 ;i<=n;i++) { dp[i][i]=初始 ...

  5. JS题目合集---新技术层出不穷,打好基础才是上策~

    在IT界中公司对JavaScript开发者的要求还是比较高的,但是如果JavaScript开发者的技能和经验都达到了一定的级别,那他们还是很容易跳到优秀的公司的,当然薪水就更不是问题了.但是在面试之前 ...

  6. Java面试经典题目合集

    32 1.”static”关键字是什么意思?Java中是否可以覆盖(override)一个private或者是static的方法? “static”关键字表明一个成员变量或者是成员方法与类相关,可以在 ...

  7. PTA L1题目合集(更新至2019.3)

    L1-001 Hello World (5 分) 链接:https://pintia.cn/problem-sets/994805046380707840/problems/9948051471320 ...

  8. 近几年杭电OJ大型比赛题目合集【更新到2017年11月初】

    2017年: 区域赛网络赛   6194~6205    6206~6216 区域赛网络赛   6217~6229 2016年: 区域赛网络赛  5868~5877    5878~5891    5 ...

  9. atcoder题目合集(持续更新中)

    Choosing Points 数学 Integers on a Tree 构造 Leftmost Ball 计数dp+组合数学 Painting Graphs with AtCoDeer tarja ...

  10. 最长上升子序列(LIS)题目合集

    有关最长上升子序列的详细算法解释在http://www.cnblogs.com/denghaiquan/p/6679952.html 1)51nod 1134 一题裸的最长上升子序列,由于N<= ...

随机推荐

  1. 玩转 Helm

    0. 前言 在 kubernetes 的系列文章中,我们介绍了 kubernetes 的种种概念,特性.不过对于如何部署并没有介绍,想象下如果 kubernetes 中 pod 的数量达到成百,上千, ...

  2. Logback 实现日志链路追踪

    本文为博主原创,未经允许不得转载: 在开发过程中,经常会使用log记录一下当前请求的参数,过程和结果,以便帮助定位问题.在并发量下的情况下,日志打印不会剧增,可以很快就能通过打印的日志查看执行的情况. ...

  3. 远程复制文件-scp

  4. Qt5.9 UI设计(六)——TitleBar功能实现

    前言 上一章介绍了ControlTreeWidget 与ControlTabWidget联动的功能,这一章我们将实现自定义 TitleBar 的功能 操作步骤 修改按键图标最大和最小值 右键按键图标, ...

  5. goofys 鲲鹏上面编译挂载与性能测试

    goofys 鲲鹏上面编译挂载与性能测试 介质 使用go进行编译. 官网上面有 amd64的介质,但是没有aarch64的介质 需要自行编译 前几天一直编译失败. 周天在家自己测试了一把,根据gith ...

  6. 【转帖】3.JVM内存结构概述

    目录 1.JVM内存结构 1.JVM内存结构 在JVM系列的第一篇文章中已经给出了JVM内存结构的简图,下面是JVM内存结构更加详细的图. 同样,JVM的内存结构可以分为上中下3层. 上层主要是类加载 ...

  7. [转帖]IO多路复用的三种机制Select,Poll,Epoll

    I/O多路复用(multiplexing)的本质是通过一种机制(系统内核缓冲I/O数据),让单个进程可以监视多个文件描述符,一旦某个描述符就绪(一般是读就绪或写就绪),能够通知程序进行相应的读写操作 ...

  8. Linux运行服务的几种方式

    摘要 1. nohup & 2. screen 3. bg & disown 4. systemd 5. crontab @reboot 背景 最近一直在用linux 想着多总结一下. ...

  9. Docker 运行 Redis Rabbitmq seata-server ftp 的简单办法

    公司里面用到了很多组件, 发现安装二进制太麻烦了, 所以想用Docker 进行安装. 这里面简单给总结一下就不在折腾了.. 1. redis docker run -d -p 6379:6379 -- ...

  10. node中的fs模块和http模块的学习

    读取文件 fs 模块 第1个参数就是要读取的文件路径 第2个参数是一个回调函数(error,data)=>{} error 如果读取失败,error 就是错误对象 如果读取成功,error 就是 ...