To_Heart—题单——SAM
感觉SAM挺神仙的,所以准备列个题单。不定时更新?
感觉SAM最重要的是它的可以不重不漏遍历完所有子串的性质。很nb啊。但是但是要不咱们背板子吧!
感觉 cmd 大佬一句话非常正确
SAM模板的学习原则 : 理解性默写。
【模板】后缀自动机 (SAM)
大概是明白了。一开始想得有点复杂甚至有点傻逼。
你会发现任意一个字串一定
[SDOI2016]生成魔咒
你发现构建 SAM 是个在线的过程。所以直接在每次在线更新的时候更新答案就好了。
[TJOI2019]甲苯先生和大中锋的字符串
先找到所有出现次数为 k 的节点。然后考虑每个节点覆盖的子串长度为 (t[t[i].link].len,t[i].len] ;然后差分加前缀和就好了。多组输入有点恶心。
CF802I
你发现这个和板题其实没什么区别,只需要在树上dp的时候把ans更改的条件变一下就好了。
[TJOI2015]弦论
考虑从根节点遍历整棵树,如果这个遍历到这个点时知道他从 a
~ z
的所有点分别可以出现再几个子串中,那么我们直接类似于 Trie01 串求异或最大值的方式找到第 k 大就好了。所以现在的问题是如何找到经过每个节点的字串个数。
首先考虑 t=0 的情况。t=0 时只有本质不同的子串才是不同的。然后考虑SAM遍历的话是可以不重不漏的遍历出每个子串的。那么经过某个点的子串个数就相当于这个点能到达的字串个数。
然后考虑 t=1 的情况。这时候位置不同就是不同的子串。考虑一个子串可以出现几次。发现一个子串最多出现次数就是 它对应的endpos集合的大小。所以把每个点的贡献从 1 改为 对应的endpos集合的大小 就好了。
最长公共子串
link.
应该是个大的板块?
大概就是对于第一个字符串,建出SAM,第二个字符串在上面跳
- 如果当前节点有关于 s i s_i si 的转移,那么直接往上面跳,当前匹配长度+1
- 如果没有,就在 Parent Tree 上往父亲跳,知道找到一个节点有关于 s i s_i si 的转移为止。如果找到了,那么当前匹配长度 = 跳到的节点的 len+1,再往 s i s_i si 的转移上跳;如果跳不到,就重置当前节点为0,当前匹配长度为 1 。
这么跳为什么是对的?因为考虑 Parent Tree 上的每父亲节点是儿子节点的后缀,而 SAM 转移方式中的当前节点一定是下一个节点的前缀。或许这才是SAM的最重要的性质?
[BJOI2020]封印
应该是做的第一道不全是SAM的题?
发现在最长公共子串中我们的算法实际上是算对于匹配串的每一位,从它开始往前和模式串的最长公共子串长度,然后再对所有位置的最长长度取max。
那么我们定义 L e n i Len_i Leni 表示从 i i i 位置开始往前和模式串的最长公共子串长度,用类似于最长公共子串的算法,可以在 O ( n ) \mathrm{O(n)} O(n) 的时间复杂度以内预处理出来。
然后你会发现一个性质:设 l i = i − L e n i + 1 l_i= i-Len_i+1 li=i−Leni+1 ,那么 l l l 数组是单增的
所以可以在询问的 l , r l,r l,r 之间二分 m i d mid mid,使得 m i d mid mid 是最小满足 l m i d ≥ l l_{mid} \ge l lmid≥l 的位置。
那么对于 mid 之前的点,它们对于答案的贡献小于等于 ( m i d − 1 ) − l + 1 (mid-1)-l+1 (mid−1)−l+1 对于 ( m i d , r ) (mid,r) (mid,r) 的点你随便写个数据结构取出来最大值和 m i d − l mid-l mid−l 比个大小就好了。
以下内容皆为口胡,有时间再写代码(