KMP 入门
再次学习 \(\rm KMP\) 后不一样的理解。
一些概念
定义字符串 \(S\) 的真 前/后 缀为非自身的 前/后 缀。
定义字符串 \(S\) 的 \(border\) 为 \(S\) 的公共真 前/后 缀。
定义字符串 \(S\) 的最长 \(border\) 为 \(\pi\),对于 \(S\) 的每个前缀 \(S_{1 \sim i}\) 令 \(\pi_i\) 为其最长 \(border\),\(\pi\) 函数就是所谓的前缀函数。
前缀函数的性质
- 相邻前缀函数 \(\pi_{i + 1} \le \pi_i + 1\)。
使用反证法易证。
- 字符串 \(S\) 的所有 \(border\) 可以由 \(\pi_{|S|}\) 开始不断跳 \(\pi\) 由长度从大到小遍历。
只需证明 \(S_{1 \sim \pi_{\pi_n}}\) 是 \(S\) 的次长 \(border\) 即可。
通过反证法也易证上述转化结论。
- 相邻前缀函数满足 \(\pi_{i + 1} = \max\limits_{T ~ is ~ the ~ border ~ of ~ S_{1 \sim i}, S_{i + 1} = S_{|T| + 1}} |T| + 1\)。
使用反证法易证。
前缀函数的线性求法
有了上述的几条性质,我们直接利用性质 \(2, 3\) 即可得到一个求解前缀函数的做法:
首先利用性质 \(3\) 的结论,大体思路上上我们通过递推从 \(\pi_{i - 1} \rightarrow \pi_i\)。
再利用性质 \(2\),不断地在 \(i\) 处从长到短跳 \(border\) 直到第一个满足 \(S_{i + 1} = S_{|T| + 1}\) 的 \(border ~ T\) 就停止,令 \(\pi_i = |T| + 1\)。
直观感受上这个做法是 \(\mathcal{O(n ^ 2)}\) 的,但实际上它是线性的,复杂度证明如下:
不难发现复杂度来源在于跳 \(\pi\),下面我们将证明跳 \(\pi\) 的总次数是线性的。
注意 \(i - \pi_i\) 的位置变化情况,不难发现其总是不会向前偏移的,而停留的次数最多不超过 \(n\) 次,总共位移长度也不超过 \(n\),显然跳 \(\pi\) 的次数是不超过上述两者之和 \(n + n = 2n\) 的。
前缀函数的基本应用
KMP
求解模式串 \(T\) 在匹配串 \(S\) 中的出现位置的问题。
市面上常见的 \(\rm KMP\) 算法是再利用 \(\pi\) 函数的性质去减少匹配次数,仔细观察其过程会发现本质上和求 \(\pi\) 函数的过程非常类似,这里我们直接将其过程划归到求 \(\pi\) 函数的问题上去。
构造一个新的字符串 \(T \# S\) 其中 \(\#\) 是一个既没有在 \(T\) 中出现也没有在 \(S\) 中出现的分隔符字符。
对这个新的字符串求其 \(\pi\) 函数,不难发现由于分隔符的出现,\(\forall i, \pi_i \le |T|\),于是找到 \(\pi_i = |T|\) 的位置,\(i - |T| + 1 \sim i\) 就是一个匹配。
统计每个前缀的出现次数
原问题等价于 \(\forall i\) 求:\(f_i = \sum\limits_{j \ge i} [S_{1 \sim j} ~ has ~ a ~ border ~ of ~ S_{1 \sim i}]\)。
直接暴力枚举是 \(\mathcal{O(n ^ 2)}\) 的,但注意到 \(border\) 的性质,我们从后往前递推解决这个问题:每次将已经求好的 \(i\) 的答案累加到 \(\pi_i\) 上即可做到 \(\mathcal{O(n)}\)。
至于若要加强成统计 \(S\) 的每个前缀在 \(T\) 中的出现次数,只需利用 \(\rm KMP\) 的构造方式,在初始赋值时只赋 \(T\) 中位置的值即可。
统计本质不同子串的数目
直接排序 + 哈希可以做到 \(\mathcal{O(n ^ 2 \log n)}\),但存在一个不带 \(\log\) 的做法。
依然考虑递推求解,每次我们在当前字符串的末尾加入一个字符,考虑答案增量。
进一步我们转化为求加入字符后有多少个后缀出现在原来的字符串中,不难发现就是反串的 \(\max \pi\),复杂度 \(\mathcal{O(n ^ 2)}\)。
字符串周期相关性质
一些定义
称 \(k\) 为 \(S\) 的一个(弱)周期当且仅当 \(\forall i, i + k \le |S|, S_i = S_{i + k}\),特别地若 \(k \nmid S\) 则称 \(k\) 为 \(S\) 的弱周期。
- 若字符串 \(S\) 存在一个 \(border ~ T\),当且仅当 \(S\) 存在一个长度为 \(|S| - |T|\) 的(弱)周期。
必要性显然,充分性递归论证即可。
- 字符串 \(S\) 的最短(弱)周期长度为 \(n - \pi_n\)。
有 \(\pi\) 函数的定义易证。
- 若长度不小于 \(p + q\) 的字符串 \(S\) 存在长度分别为 \(p, q\) 的(弱)周期,那么 \(\gcd(p, q)\) 也是 \(S\) 的一个(弱)周期。
下面首先证明 \(p - q(p > q)\) 也是 \(S\) 的一个(弱)周期。
分两种情况讨论:
若 \(i \le q\),则 \(S_i = S_{i + p} = S_{i + p - q}\),可知 \(\forall i \le q, p - q\) 是其一个(弱)周期。
若 \(i > q\),则 \(S_i = S_{i - q} = S_{i + p - q}\),可知 \(\forall i > q, p - q\) 是其一个(弱)周期。
证明 \(p - q\) 是 \(S\) 的一个(弱)周期后,根据更相减损术的性质,最终可以迭代至 \(\gcd(p, q)\) 是 \(S\) 的一个(弱)周期。
- 所有周期的长度均为最短周期长度的倍数。
设最短周期为 \(p\),若 \(\exist q > p, p \nmid q\) 且 \(q\) 也为一个周期,可知 \(p \mid |S|, q \mid |S|\),又 \(p, q \ne |S|\),则 \(p, q \le \frac{|S|}{2} \Rightarrow p + q \le |S|\),再根据性质 \(3\),有 \(\gcd(p, q) < p\) 也为一个周期,矛盾。
- 字符串 \(S\) 存在周期当且仅当 \(n - \pi_n\) 为一个周期。
必要性显然,充分性证明如下:
令 \(p = n - \pi_n\) 若 \(\exist q, q\) 为 \(S\) 的一个最小周期,那么可知 \(p < q \le \frac{|S|}{2}\),则 \(p + q < |S|\),根据性质 \(3\),\(\gcd(p, q) < q\) 且 \(\gcd(p, q)\) 为 \(S\) 的一个周期,矛盾。
KMP 自动机
在求 \(\pi\) 函数的时候,我们发现这个做法是支持在线插入一个字符在末尾并计算函数值的。
并且在做 \(\rm KMP\) 的过程中我们发现,在匹配串每一位求 \(\pi\) 函数时,并不关心之前匹配串的字符是什么,只关心之前的 \(\pi\) 函数值和当前位的字符。
这意味着我们可以对一个模式串建立一个自动机,每个位置上状态为当前在模式串的第几位,暴力建立这个自动机复杂度是 \(\mathcal{O(n ^ 2|\sum|)}\),因为这里不存在求 \(\pi\) 函数指针单方向移动的性质。
但实际上每次一直跳 \(\pi\) 是很浪费的,因为跳一次以后的答案之前都已经计算完毕,因此可以直接调用,于是建立自动机的复杂度就为 \(\mathcal{O(n|\sum|)}\)。
\(\rm KMP\) 自动机一般用来解决特殊的匹配问题,比如:特殊字符串的匹配问题,但因为 \(\rm KMP\) 自动机用处不是很多,我也没有遇到需要的题目,在此先不再讲述。
KMP 入门的更多相关文章
- zstu.4194: 字符串匹配(kmp入门题&& 心得)
4194: 字符串匹配 Time Limit: 1 Sec Memory Limit: 128 MB Submit: 206 Solved: 78 Description 给你两个字符串A,B,请 ...
- 题解报告:hdu 2087 剪花布条(KMP入门)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2087 Problem Description 一块花布条,里面有些图案,另有一块直接可用的小饰条,里面 ...
- hdu 1358 Period(KMP入门题)
Period Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Subm ...
- KMP入门题目[不定期更新]
HDU 1711 Number Sequence(模板题) #include <cstdio> ; ; int N, M; int textS[MAXN]; int tarS[MAXL]; ...
- KMP入门(匹配)
Description Given two sequences of numbers : a[1], a[2], ...... , a[N], and b[1], b[2], ...... , b[M ...
- hdu 1358 period KMP入门
Period 题意:一个长为N (2 <= N <= 1 000 000) 的字符串,问前缀串长度为k(k > 1)是否是一个周期串,即k = A...A;若是则按k从小到大的顺序输 ...
- hdu 1686 & poj 2406 & poj 2752 (KMP入门三弹连发)
首先第一题 戳我穿越;http://acm.hdu.edu.cn/showproblem.php?pid=1686 题目大意好理解,每组输入一个子串和一个母串,问在母串中有多少个子串? 文明人不要暴力 ...
- HDU2203(KMP入门题)
亲和串 Time Limit: 3000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submiss ...
- HDU2087(KMP入门题)
剪花布条 Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submis ...
- HUD1686(KMP入门题)
Oulipo Time Limit: 3000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Subm ...
随机推荐
- TGAN
目录 概 主要内容 Saito M., Matsumoto E. & Saito S. Temporal Generative Adversarial Nets with Singular V ...
- Unity——基于ShaderLab实现光照系统
这篇主要总结Unity中ShaderLab的着色器代码实现总结,需要有一定图形学基础和ShaderLab基础: 一.着色器 1.顶点片元着色器 分顶点着色器和片元着色器,对应渲染管线的顶点变换和片元着 ...
- SpringBoot读取外部配置文件的方法
SpringBoot读取外部配置文件的方法 Spring高级之注解@PropertySource详解(超详细) 1.@PropertySource(value = {"classpath:c ...
- SpringBoot+神通数据库+JPA
先上原文 https://blog.csdn.net/Helloworld_pang/article/details/114266130 一.SpringBoot + 神通数据库 基本上按照上面的参考 ...
- 分区命令(大于2TB的分区)
注意:parted命令在恢复误删除的分区时候,容易失败的几点: (1)只划分一个分区.恢复失败 (2)划分了2个分区,但是没有格式化.直接删除一个分区,恢复也会失败. (3)做删除操作时候,如果同时删 ...
- CSS基础 BFC的使用方法
BFC的作用和创建1.html标签是BFC盒子2.浮动元素是BFC盒子3.行内块元素是BFC盒子4.overflow属性值不为visible,如:auto.hidden...作用:1.清除浮动: 2. ...
- VirtualBox虚拟机安装win8/10
你可能会遇到过,需要win8来做一些操作,不过自己的本机是win7,难道要重装系统吗?操作好了后,想用回win7怎么办?这个时候,如果旁边有人的系统刚好符合你对系统的要求,那可以借用,如果使用时间太长 ...
- Centos7 安装 brctl 工具
[root@docker-node1 ~]# brctl show -bash: brctl: command not found [root@docker-node1 ~]# yum -y inst ...
- [ css ] 实现漂亮的输入框动画(借鉴自panjiachen的后台管理项目)
效果预览 HTML <div class="l-custom-input"> <input size="large" id="l-i ...
- 安装KVM
在VMWare安装CentOS7 选择图形界面和开发工具 设置网络 cd /etc/sysconfig/network-scripts/ vi ifcfg-ens33 BOOTPROTO=static ...