2022.9.22 闲话
TMD 做 CF1C 一直 WA13,3 机房上不去 CF 是啥玩意???我为啥不能给 Eafoo 的博客评论???难道我被认知危害了吗????
我打算放 メリーバッドエンド (Merry Bad End) 歌词,现在假装下面有歌词:
メリーバッドエンド
Merry Bad End
KMP 自动机相关的一些杂谈
提到 KMP,给大家提供一道水题:Ynoi2011 遥远的过去(有点码农,我可能要鸽挺久了 TAT
KMP 自动机
以前一直以为 KMP 算法本来就是一个天然的自动机,直到看到 CF1721E 才发现原生 KMP 没有 fail 指针 QwQ .
现在来看一下真正的 KMP 自动机 .
从前缀 border 开始
KMP 的一个开端也是前缀的 border,一些关于 border 的东西可以看 Border Theory,理解不深刻轻喷(实际上目前 IPM 还没有几个 OIer 实现出来).
约定:首先后面的东西都是对于一个原串 \(S\) 来说的,不妨令其字符集为 \(\Sigma\),其他约定见前面的 Border Theory .
定义前缀函数(也就是 next 数组)为 \(\pi_i\) 表示 \(S[1:i]\) 的最长 border 长度 .
KMP 算法指出了一个 \(O(|S|)\) 计算 next 数组的方法,不是很容易用语言简单表述就挂个链接跑路吧 XD:KMP - OI Wiki .
注意到它对于每一位的计算是 均摊 \(O(1)\) 的,这就表明它不适用于动态问题 .
Border 树
对于每个点 \(i\),连边 \(i\to\pi_i\),于是前面的过程就是在 Border 树上跳 .
具体的原因可以看 Border Theory,事实上是非常自然的 .
于是我们可以自然的引入 fail 指针(怎么都是自然的):
fail 指针
为了让 KMP 更像自动机一点,我们先让每个点可以向任意一个字符转移,定义广义前缀函数 \(\pi_{i,c}\) 表示表示从 \(i\) 开始沿着 Border 树向上跳得到的最大的位置 \(k\text{ s.t. }s_{k+1}=c\) .
然后引入 fail 指针 \(f_i=\pi_{f_{i-1},S_i}\) .
这样我们的 \(\pi\) 就可以单次严格 \(O(|\Sigma|)\) 递推了:
- 若 \(S_{i+1}=c\),则 \(\pi_{i,c}=i\) .
- 若 \(S_{i+1}\neq c\),则 \(\pi_{i,c}=\pi_{f_i,c}\) .
看起来就是 AC 自动机转移啊?事实上 KMP 自动机也就是单串的 AC 自动机了 .
例题 / Prefix Function Queries
给一个字符串 \(S\),\(q\) 组询问,每次给一个字符串 \(T\),求 \(S+T\) 的后 \(T\) 位的前缀函数值 .
\(1\le |S|\le 10^6\),\(1\le q\le 10^5\),\(1\le |t|\le 10\),字符集为小写字母 .
每次加一个字符只需要改一位 next 一位 fail,然后就随便做了 .
时间复杂度 \(O(|\Sigma|(|S|+\sum|t|))\) .
例题 / Anthem of Berland
给两个串串 \(s,t\),\(s\) 中可能有问号 .
把每个问号换成一个小写字母,问 \(t\) 匹配 \(s\) 的次数最大是多少 .
\(1\leq |s|,|t|\leq 10^5\),\(|s|\cdot |t|\leq 10^7\) .
除了问号字符集是小写字母 .
KMP 自动机上 DP .
令 \(dp_{i,j}\) 表示 \(s[1:i]\) 走到 KMP 自动机上的节点 \(j\) 时匹配的最大次数,转移显然 .
时间复杂度 \(O(|s|\cdot |t|\cdot |\Sigma|)\) .
其实可以类比 AC 自动机上 DP 的啦 .
可持久化 KMP
好像是叫这个名字 /yun .
首先放一下题面,免得大家不知道我在说什么:
维护一个字符串 \(S\)(初始为空),\(n\) 次操作:
- 在末尾添加一个字符 .
- 删掉末尾的字符 .
\(1\le n\le 2\times 10^5\) .
下面提供一个 KMP 自动机做法 .
首先建 KMP 自动机,点数记作 \(m\),根记作 \(r\) .
对于一个点,我们需要求的就是既是 Border 树上的祖先又是 fail 树上的祖先中深度最大的那个,先预处理出 Border 树的 DFS 序,\(u\) 点的 DFS 序记作 \(\operatorname{dfn}(u)\),然后还要顺便记一下每个点的子树 DFS 序的 max,记作 \(\operatorname{maxdfn}(u)\) .
在 fail 树上 DFS,走到一个点 \(u\) 的时候,如果 \(u\leftrightarrow r\) 的路径上存在一个点 \(v\) 使得 \(\operatorname{dfn}(v)\in[\operatorname{dfn}(u),\operatorname{maxdfn}(u)]\) 的时候,\(v\) 就可能作为答案,于是建一棵线段树,每个点存一个链表,每次访问点 \(u\) 的时候对于 \([\operatorname{dfn}(u),\operatorname{maxdfn}(u)]\) 上的每个点后面都链一个 \(u\),走出去的时候就删掉,于是查询就只需要查询线段树中到 \(\operatorname{dfn}(u)\) 的路径中深度最大的点,这个不难维护 .
时间复杂度大概是 \(\Theta(n\log n)\) .
还有一些其他做法可以做到 \(\Theta(n)\),这里就不说了 .
以下是博客签名,正文无关
本文来自博客园,作者:Jijidawang,转载请注明原文链接:https://www.cnblogs.com/CDOI-24374/p/16720154.html
版权声明:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0)进行许可。看完如果觉得有用请点个赞吧 QwQ