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 ...
随机推荐
- Dubbo 的设计思想
在java远程调用多年的沉淀 <1>首先是socket调用.在orderService中开放socket服务,在userService中进行远程调用. 优点:解决了单机调用的问题. 缺点: ...
- matplotlib 高阶之patheffect (阴影,强调)
目录 添加阴影 使Artist变得突出 更多效果 我们可以通过path来修饰Artist, 通过set_path_effects import matplotlib.pyplot as plt imp ...
- 手机端h5页面 图片根据手势放大缩小
pinchzoom.js 这个插件可以简单的实现这一功能 <div class="big_pos_img page"> <div class="pinc ...
- [炼丹术]使用Pytorch搭建模型的步骤及教程
使用Pytorch搭建模型的步骤及教程 我们知道,模型有一个特定的生命周期,了解这个为数据集建模和理解 PyTorch API 提供了指导方向.我们可以根据生命周期的每一个步骤进行设计和优化,同时更加 ...
- 云南农职《JavaScript交互式网页设计》 综合机试试卷③——实现二级分类菜单
一.语言和环境 实现语言:HTML,CSS,JavaScript,JQuery. 开发环境:HBuilder. 二.题目(100分): 使用Jquery和JavaScript实现二级分类菜单管理 点击 ...
- 年功序列c++游戏
题目描述 在虚拟国度里多了很多 Virtual oier,为了树立对后辈的威信,从第 11 个 Virtual oier 开始的 oier 们搞起了年功序列的制度. 虚拟国度的创始人 oier Cht ...
- Typescript Record的用法
Record<Keys,Type> 构造一个对象类型,其属性key是Keys,属性value是Tpye.被用于映射一个类型的属性到另一个类型 interface CatInfo { age ...
- centos7 安装locate
使用locate my.cnf命令可以列出所有的my.cnf文件 yum -y install mlocate 原因是安装完后没有更新库 更新库:updatedb
- [Flask] 安装virtualenv时候出现的问题
1.HTTPError: 404 Client Error: Not Found for url: ...的错误 ubuntu@VM-0-6-ubuntu:~/myprojects$ virtuale ...
- 《手把手教你》系列技巧篇(五十六)-java+ selenium自动化测试-下载文件-上篇(详细教程)
1.简介 前边几篇文章讲解完如何上传文件,既然有上传,那么就可能会有下载文件.因此宏哥就接着讲解和分享一下:自动化测试下载文件.可能有的小伙伴或者童鞋们会觉得这不是很简单吗,还用你介绍和讲解啊,不说就 ...