SAM(后缀自动机)专题总结
这是一篇其实理解并不深刻只会打板子的蒟蒻写出的总结
分成几个板块吧。。。。。。
- 1.检查字符串是否出现
给一个文本串 \(T\) 和多个模式串 \(P\),我们要检查字符串 \(P\) 是否作为 \(T\) 的一个子串出现。
\ 对 \(T\) 建出 \(SAM\)
\ 直接在后缀树上从根开始往下走,如果能走到 \(P\) 结尾说明是模板串的子串
- 2.不同子串个数
给一个字符串 \(S\),计算不同子串的个数。
\ 静态:我们知道每个子串就是后缀DAG上的一条路径
\ \ \ \ \ \ DAG上路径数怎么统计就不用说了吧
\ 动态:每次新建一个节点,贡献为\(len(np)-len(f(np))\)
\ \ \ \ \ \ 好像挺显然的?
例题:生成魔咒
- 3.最小循环移位
给定一个字符串 \(S\) 。找出字典序最小的循环移位
发现字符串 \(S+S\) 包含字符串 \(S\) 的所有循环移位作为子串。
所以问题变为在 \(S+S\) 对应的后缀自动机上寻找最小的长度为 |S| 的路径
直接从初始状态开始,贪心地访问最小的字符即可。
例题:工艺
- 4.出现次数
对于一个给定的文本串 \(T\) ,有多组询问,每组询问给一个模式串 \(P\) ,回答模式串 \(P\) 在字符串 \(T\) 中作为子串出现了多少次
我们发现用如果模式串在 \(SAM\) 上跑匹配,那么最终到达的点的 \(endpos\) 就是该串的出现次数
考虑 \(endpos\) 的处理根据定义发现其实就是后缀树上的子树大小
所以我们把实点权值设为 \(1\),虚点设为 \(0\),跑拓扑就行了
例题:\(substring\)(需要动态维护 \(endpos\),打棵 \(lct\) 呗)
- 5.字典序第 k 大子串
给定一个字符串 \(S\) 。多组询问,每组询问给定一个数 K ,查询 \(S\) 的所有子串中字典序第 K 大的子串。
字典序第 \(K\) 大的子串对应于 \(SAM\) 中字典序第 \(k\) 大的路径
那么在计算每个状态的路径数后,就可以从 \(SAM\) 的根开始找到第 \(k\) 大的路径。
例题:弦论
- 6.第一次出现的位置
给定一个文本串 \(T\) ,多组查询。每次查询字符串 \(P\) 在字符串 \(T\) 中第一次出现的位置( \(P\) 的开头位置)。
预处理出每个状态第一次出现的位置\(pos(i)\)
其实只需要让每次\(pos(np)=len(np) \ pos(nq)=pos(q)\)就好了
查询答案为\(pos(i)-|T|+1\)
- 7.最短的没有出现的字符串
给定一个字符串 \(S\) 和一个特定的字符集 \(T\),我们要找一个长度最短的没有在 \(S\) 中出现过的字符串
在 \(SAM\) 上做 \(dp\)
设 \(dp_i\) 表示到点 \(i\) 时的最短长度
如果这个点有不是 \(T\) 中字符的出边,则 \(dp_i=1\),否则 \(dp_i=1+\min\limits_{(i,j,c)\in SAM}dp_j\)
- 8.两个字符串的最长公共子串
给定两个字符串 \(S\) 和 \(T\) ,求出最长公共子串。
直接把 \(T\) 扔到 \(S\) 的自动机上跑匹配就行了
- 9.求 \(endpos\) 集合
给定一个字符串 \(S\),求 \(endpos\) 集合
首先我们能够求出每个节点的 \(pos\)
然后发现一个点的 \(endpos\) 就是他子树的 \(pos\) 的集合
怎么让一个点带上整个子树的某个值? 主席树合并啊!
每个点初始在主席树上插入 \(pos(i)\)
然后拓扑合并就行了
例题:你的名字