SAM题目合集

一些SAM的 基础 题目。(主要是我不想写SAM的原理啊啊啊)
有的题目是SA的思维题,但是可以用SAM平推,基本上可以不动脑子。
除非有特殊说明,否则将字符集看作所有小写字母,构造SAM复杂度记为 O(|S|)

基础SAM

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

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

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

Luogu P2408 不同子串个数

S 串的不同子串个数。

等价于 SAM 上路径数,DP求值即可。
总体复杂度 O(|S|)

SPOJ LCS-Longest Common Substring

求串 ST 的最长公共字串。

等价于求 T 前缀中在 S 的 SAM 上出现的最长后缀的最大值。
考虑在 S 的 SAM 上跑 T 串,假设当前在点 v ,最长后缀长度为 l ,加入字符 ti
如果 v 存在 ti 对应转移, vnxtv(ti),ll+1 即可,
否则 vlink(v),llen(link(v)) ,及将 v 跳向 link 边,直至跳至 1 或存在 ti 的转移为止。
由于 l 只会变大 |T| 次,变小 O(|T|) 次,所以总体复杂度为 O(|S|+|T|)

[SDOI2016]生成魔咒

动态求不同字串个数。

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

[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,li,ri ,求 S[liri]T 的最长公共子串。

先建出 T 的 SAM ,对于 S 的每一个前缀,求出最长的是 T 子串的后缀 ai
最终答案相当于是 maxi=lr(min(ai,il+1))
将所有询问离线下来,用线段树维护答案即可。
总体复杂度为 O(|T|+|S|log|S|+Qlog|S|)

CF235C Cyclical Quest

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

考虑将 T 变成 TT ,题目就等价于询问 TT 中所有不同的长度为 |T| 的子串在 S 中的出现次数之和。
S 建 SAM ,维护 TTS 中的匹配,可以一直跳 link 跳到 len(link(v))|T| 为止,由于需要不同的串,所以SAM中的每一个点只会对答案做一次贡献。
总体复杂度 O(|S|+|T|)

CF427D Match & Catch

ST 中均只出现过一次的串的长度的最小值。

考虑建出 S 串的 SAM ,让 T 在上面跑匹配。
与一般匹配不同的是,我们还需要维护 v 的所有 link 树上祖先,都记录下长度为多少的串出现了多少次。
但由于这个值是单调的,所以只需要维护出经过它的最大的两个 min(len(v),l) 即可。
最后在所有 |endpos(v)|=1 的点中找答案。
总体复杂度 O(|S|2)

[HAOI2016] 找相同字符

给定两个字符串 S1S2,问有多少个对不同的 S1 的子串和 S2 的子串相等。

考虑某一个字符串的贡献,等于 S1×S2
对应到 S1|S2 上,找到该字符串对应节点,贡献即为 |endposS1|×|endposS2|
所以,最终答案为 (len(v)len(link(v)))|endpos(v)S1|×|endpos(v)S2|
总体复杂度 O(|S1|+|S2|)

[CTSC2012]熟悉的文章

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

考虑将所有的串 T 拼在一起,在间隔处插入一个标识符,记为 T0 ,这样可以对所有 T 一起建 SAM 保证复杂度。
找到 S 中每一个前缀在 T0 的最长后缀匹配,记为 ai ,考虑二分答案求解 L
使用 DP 维护单次转移:记 fi 表示前 i 位,与 T0 匹配长度的最大值。
fi=max(fi1,maxiaijiL(fj+ij))=max(fi1,maxiaijiL(fjj)+i)
由于 iaiiL 都是不减的,可以使用单调队列维护转移。
整体复杂度 O(|T|+|S|log|S|)

CF653F Paper task

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

考虑建出SAM,一个字符串的贡献我们在它对于 endpos 集合中最小的位置计算,则所有的本质不同字符串对应 O(n) 个形如 S[l,r],l[a,b] 的区间。
使用动态开点线段树维护以每一个位置为结尾有那些起点对应合法的括号串,直接区间查询即可。
时间复杂度 O(nlogn)

SAM+可持久化线段树合并

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

给国的字符串

询问 Q 个串 Ti ,在 S 中可以找到多少个互不相交的子串 Ti

n=|S|,m=|Ti|
如果已经知道了 Ti 作为 S 的字串出现的所有位置,不难证明贪心的从左到右选可以选的是最优的策略。
尝试维护这个贪心的过程,我们可以通过在 SAM 上维护每一个节点的 endpos 来实现 anslogn 的查询。
但是,我们发现对于 ans 极大,如 aaaaa...aa 出现的次数,会被卡到 TLE 。
考虑根号分治: 对于 |Ti|>B ,必然存在 ans<nB ,暴力求解的最劣复杂度为 O(nBlogn) ;对于 |Ti|<B ,我们考虑预处理出来所有的长度 BS 的字串,做到在 SAM 中匹配到对应节点之后 O(1) 查询。
由于一次暴力维护是 anslogn 的,而 S 中长度 B 的串只有 n+(n1)++(n+1B)=nBB(B1)2 个,所以暴力维护的 ans<nB ,暴力维护答案复杂度小于 nBlogn
由于 |Ti|=m ,所以长度大于 B 的串只会出现至多 O(mB) 个,所以最终复杂度为 O(nBlogn+mB×nBlogn)=O(nBlogn+mnB2logn)
B=O(m3) 时,复杂度取到最优 O(nm3logn)

[NOI2018] 你的名字

给定字符串 SQ 次询问,求 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[1,r] 中最大的点 g ,如果 g 不存在或 g<l ,则需要跳到 link(v) ,重复此操作。
也有一种可能,就是 min(p,gl+1)<len(link(v)) ,其中 p 为最长匹配长度,这中情况下选择 v 不优于选择 link(v) ,所以也要跳到 link(v)
总体复杂度 O(|S|+|T|log|S|)

CF1037H Security

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

考虑建出 S 的 SAM ,同时使用可持久化线段树合并维护 endpos
TS 中进行匹配,维护每一次对应匹配的字符在 S 中的位置,初始为 l1 ,每次找到 [las+1,r] 中最小的 endpos
如果没有,则表示 T 无法匹配、
找到 TS[l,r] 的最长前缀之后,从后往前一次枚举,知道找到某一位的存在一个大于 T 的串,弹出并输出答案。
总体复杂度 O(|S|+26|T|log|S|) ,但是显然跑不满 26 这个常数。

[HEOI2016/TJOI2016]字符串

给定字符串 SQ 次询问,求最长的 len 使得 S[cd] 中长为 len 的后缀是 S[ab] 的子串。

首先将 S 串反转,将前缀变成后缀。
由于答案是可二分的,考虑二分答案:
我们通过 link 树上倍增找到 S[dlen+1d] 在 SAM 中的位置,查找是否存在在 [a+len1,b]endpos 即可。
总体复杂度 O(|S|log2|S|)

CF666E Forensic Examination

给定字符串 S,和一个些字符串 T1,T2TmQ 次询问,问在 TLTL+1TR 中,S[l,r] 出现次数最多的串中编号最小是哪一个,并输出出现次数。

考虑建出 S|T1|T2||Tm 的 SAM,其中 '|' 为分隔符。
使用可持久化线段树合并维护每个点的 endpos 中位置对应第 Tj,j=1,2m 个串的分别有多少个。
查询 S[l,r] 的出现次数,考虑从 S[1,r] 对应的节点进行 link 树上倍增找到对应节点,在线段树上区间取值即可。
总体复杂度 O(|S|+|T|log(|T|)+Qlog(|S|+|T|))

广义SAM

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

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

询问若干个串 S1,S2Sn 中一共有多少个本质不同的子串。

就是基本的建立 SAM 的过程,最后求出 len(v)len(link(v)) 即可。
总体复杂度 O(|S|)

CF452E Three strings

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

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

CF204E Little Elephant and Strings

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

建出广义SAM,使用动态开点线段树维护出 endpos 集合在每一个串中有多少个,如果这个 endpos 中有 k 个串,则令这些数均做 len(v)len(link(v)) 的贡献。
link 树上跑线段树合并即可得到答案。
总体复杂度 O(|S|logn)

posted @   Xun_Xiaoyao  阅读(78)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
/* 鼠标点击求赞文字特效 */
点击右上角即可分享
微信分享提示