再次学习 \(\rm KMP\) 后不一样的理解。

一些概念

  1. 定义字符串 \(S\) 的真 前/后 缀为非自身的 前/后 缀。

  2. 定义字符串 \(S\) 的 \(border\) 为 \(S\) 的公共真 前/后 缀。

  3. 定义字符串 \(S\) 的最长 \(border\) 为 \(\pi\),对于 \(S\) 的每个前缀 \(S_{1 \sim i}\) 令 \(\pi_i\) 为其最长 \(border\),\(\pi\) 函数就是所谓的前缀函数。

前缀函数的性质

  1. 相邻前缀函数 \(\pi_{i + 1} \le \pi_i + 1\)。

使用反证法易证。

  1. 字符串 \(S\) 的所有 \(border\) 可以由 \(\pi_{|S|}\) 开始不断跳 \(\pi\) 由长度从大到小遍历。

只需证明 \(S_{1 \sim \pi_{\pi_n}}\) 是 \(S\) 的次长 \(border\) 即可。

通过反证法也易证上述转化结论。

  1. 相邻前缀函数满足 \(\pi_{i + 1} = \max\limits_{T ~ is ~ the ~ border ~ of ~ S_{1 \sim i}, S_{i + 1} = S_{|T| + 1}} |T| + 1\)。

使用反证法易证。

前缀函数的线性求法

有了上述的几条性质,我们直接利用性质 \(2, 3\) 即可得到一个求解前缀函数的做法:

  1. 首先利用性质 \(3\) 的结论,大体思路上上我们通过递推从 \(\pi_{i - 1} \rightarrow \pi_i\)。

  2. 再利用性质 \(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\) 的弱周期。


  1. 若字符串 \(S\) 存在一个 \(border ~ T\),当且仅当 \(S\) 存在一个长度为 \(|S| - |T|\) 的(弱)周期。

必要性显然,充分性递归论证即可。

  1. 字符串 \(S\) 的最短(弱)周期长度为 \(n - \pi_n\)。

有 \(\pi\) 函数的定义易证。

  1. 若长度不小于 \(p + q\) 的字符串 \(S\) 存在长度分别为 \(p, q\) 的(弱)周期,那么 \(\gcd(p, q)\) 也是 \(S\) 的一个(弱)周期。

下面首先证明 \(p - q(p > q)\) 也是 \(S\) 的一个(弱)周期。

分两种情况讨论:

  1. 若 \(i \le q\),则 \(S_i = S_{i + p} = S_{i + p - q}\),可知 \(\forall i \le q, p - q\) 是其一个(弱)周期。

  2. 若 \(i > q\),则 \(S_i = S_{i - q} = S_{i + p - q}\),可知 \(\forall i > q, p - q\) 是其一个(弱)周期。

证明 \(p - q\) 是 \(S\) 的一个(弱)周期后,根据更相减损术的性质,最终可以迭代至 \(\gcd(p, q)\) 是 \(S\) 的一个(弱)周期。

  1. 所有周期的长度均为最短周期长度的倍数。

设最短周期为 \(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\) 也为一个周期,矛盾。

  1. 字符串 \(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 入门的更多相关文章

  1. zstu.4194: 字符串匹配(kmp入门题&& 心得)

    4194: 字符串匹配 Time Limit: 1 Sec  Memory Limit: 128 MB Submit: 206  Solved: 78 Description 给你两个字符串A,B,请 ...

  2. 题解报告:hdu 2087 剪花布条(KMP入门)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2087 Problem Description 一块花布条,里面有些图案,另有一块直接可用的小饰条,里面 ...

  3. hdu 1358 Period(KMP入门题)

    Period Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Subm ...

  4. KMP入门题目[不定期更新]

    HDU 1711 Number Sequence(模板题) #include <cstdio> ; ; int N, M; int textS[MAXN]; int tarS[MAXL]; ...

  5. KMP入门(匹配)

    Description Given two sequences of numbers : a[1], a[2], ...... , a[N], and b[1], b[2], ...... , b[M ...

  6. hdu 1358 period KMP入门

    Period 题意:一个长为N (2 <= N <= 1 000 000) 的字符串,问前缀串长度为k(k > 1)是否是一个周期串,即k = A...A;若是则按k从小到大的顺序输 ...

  7. hdu 1686 & poj 2406 & poj 2752 (KMP入门三弹连发)

    首先第一题 戳我穿越;http://acm.hdu.edu.cn/showproblem.php?pid=1686 题目大意好理解,每组输入一个子串和一个母串,问在母串中有多少个子串? 文明人不要暴力 ...

  8. HDU2203(KMP入门题)

    亲和串 Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submiss ...

  9. HDU2087(KMP入门题)

    剪花布条 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submis ...

  10. HUD1686(KMP入门题)

    Oulipo Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Subm ...

随机推荐

  1. Amazing!!CSS 也能实现烟雾效果?

    最近利用 CSS 实现了一些看似超出 CSS 能力的效果: 巧用渐变实现高级感拉满的背景光动画 Amazing!!CSS 也能实现极光? 本文继续此系列,本文主要想探讨一下,使用 CSS 能否比较好的 ...

  2. contrastive CAM

    目录 概 主要内容 一个有趣的应用 > Prabhushankar M., Kwon G., Temel D. and AlRegib G. Contrastive explanation in ...

  3. matplotlib 高阶之Transformations Tutorial

    目录 Data coordinates Axes coordinates Blended transformations 混合坐标系统 plotting in physical units 使用off ...

  4. Eclipse导包

    导包快捷键:"Ctrl+Shift+M",但是一般不用,一般利用整理包的快捷键. 整理包的快捷键:"Ctrl+Shift+O",与导包的区别在于,有用的留着,没 ...

  5. spring boot 启动读取的配置文件优先级

    1.优先级从高到低 1.  file:/config/ 2. file:/ 3. classpath:/config/ 4. classpath:/ 所有位置的application.properti ...

  6. Go标准库之html/template

    html/template包实现了数据驱动的模板,用于生成可防止代码注入的安全的HTML内容.它提供了和text/template包相同的接口,Go语言中输出HTML的场景都应使用html/templ ...

  7. spring boot 中使用swagger

    一.pom.xml <dependency> <groupId>io.springfox</groupId> <artifactId>springfox ...

  8. HDU-1004(C语言描述)

    Let the Balloon Rise 输入 输入包含多个测试用例.每个测试用例都以数字 N (0 < N < = 1000) 为起点, 分布的气球总数.下 N 行包含一个颜色.气球的颜 ...

  9. Docker入门篇(一)安装docker

    Docker入门篇(一)安装docker Docker的来源 由dotCloud公司首创及正式命名,但是企业规模小,影响力不够,所以在快要坚持不住的时候,开始吃百家饭--开源了.不开则已,一开惊人.越 ...

  10. 获取app启动时间

    启动APP并收集消耗时间的命令: adb shell am  start -W -n package/activity 手动关闭谷歌浏览器APP(也可以使用命令关闭adb shell am force ...