Z-algorithm

Algorithm

Task

给定一个文本串 \(S\) 和一个模式串 \(T\),求 \(T\) 对于 \(S\) 的每个后缀子串的公共前缀子串。

Limitations

要求时空复杂度均为线性

Solution

设 \(X\) 是一个字符串,则以下表述中,\(X_u\) 代表 \(X\) 的第 \(u\) 个字符,\(X_{u \sim v}\) 代表 \(X\) 的从 \(u\) 起到 \(v\) 结束的字串。

设 \(n = |S|,~m = |T|\)。

考虑按照长度由大到小扫描 \(S\) 的后缀字串,设当前要求 \(S_{i \sim n}\) 与 \(T\) 的公共前缀子串,则 \(\forall k \in [1, ~i),~S_{k \sim n}\) 的答案都已计算完成。

设先前的计算中,匹配到 \(S\) 最远的一次为第 \(p\) 次,即 \(p + ans_p\) 在所有 \(k + ans_k\) 中最大,设 \(q = p + ans_p\)。显然有 \(p < i\)。

首先不妨设 \(q \geq i\)。\(q < i\) 的情况将在下方说明。

设 \(j = i - p + 1\),不难发现 \(T_j = S_i\),即 \(j\) 是 \(S_i\) 的对应匹配位置。

由于所求是公共前缀字串,因此有

\[S_{p \sim q} = T_{1 \sim ans_p}
\]

引入一组辅助变量,设 \(next_j\) 为 \(T_{j \sim m}\) 与 \(T\) 的最长公共前缀子串长度。

根据定义,有

\[T_{j \sim j + next_j} = T_{1 \sim next_j}
\]

分两种情况讨论。

第一种情况,\(j + next_j < q\),即 \(T_{j \sim j + next_j}\) 是 \(S_{p \sim q}\) 的字串,因此有

\[T_{j \sim j + next_j} = S_{i \sim i + next_j}
\]

又因为 \(T_{j \sim j + next_j} = T_{1 \sim next_j}\)(已证),等量代换得到

\[S_{i \sim i + next_j} = T_{1 \sim next_j}
\]

对于 \(S_i + next_j + 1\),则可以用反证法证明其不等于 \(T_{next_j + 1}\),否则由于 \(T_{j + next_j + 1}\) 依然是 \(S_{p \sim q}\) 的字串,所以 \(T_{j + next_j + 1} = T_{next_j + 1}\),这与 \(next_j\) 是最长前缀公共子串矛盾。

因此,对于这种情况,答案即为 \(next_j\)。

第二种情况,\(j + next_j \geq q\),即 \(T_{j \sim j + next_j}\) 不全是是 \(S_{p \sim q}\) 的字串,因此有

首先可以用与第一种情况相同的证明方式证明 \(S_{i \sim q} = T_{1 \sim q - i + 1}\),即 \(q\) 及以前的字符可以与 \(T\) 完美匹配,而对于 \(q\) 后面的字符,我们暴力将其与 \(T\) 匹配,同时更新 \(p\) 和 \(q\) 的位置即可。

对于 \(q < i\) 的情况,显然 \(q = i - 1\),直接继续暴力进行匹配即可。

考虑时间复杂度:

除掉暴力匹配的环节,剩下的部分显然都是单次 \(O(1)\) 完成,因此这一部分的复杂度是线性的。

考虑每次暴力匹配都会让 \(q\) 右移,所以暴力匹配的次数是线性的,而单次暴力匹配是 \(O(1)\) 的,因此算法的时间复杂度是线性的。

考虑 \(next\) 数组的计算:我们发现这相当于令文本串 \(S = T\),只需要预处理 \(next_1\) 与 \(next_2\),可以发现从 \(next_3\) 起,计算所需要的 \(next\) 值都已经在之前被计算过。

Sample

【P5410】 【模板】扩展 KMP

Description

给定一个文本串 \(S\) 和一个模式串 \(T\),求 \(T\) 对于 \(S\) 的每个后缀子串的公共前缀子串。并输出 \(T\) 的每个后缀字串与 \(T\) 的公共前缀子串长度。

Limitations

字符串长度不超过 \(10^5\)

Solution

板板题。算法在实现上比较吃细节,注意比较大于小于的时候是否应该加等于号。记得对拍

Code

#include <cstdio>
#include <cstring>
#include <algorithm> const int maxn = 100005; int nxt[maxn];
char S[maxn], T[maxn]; void Z_algorithm(const char *const A, const char *const B, const int x, const int y, const bool pt); int main() {
freopen("1.in", "r", stdin);
scanf("%s\n%s", S + 1, T + 1);
int x = strlen(S + 1), y = strlen(T + 1);
nxt[1] = y;
Z_algorithm(T, T, y, y, false);
for (int i = 1; i <= y; ++i) {
qw(nxt[i], i == y ? '\n' : ' ', true);
}
Z_algorithm(S, T, x, y, true);
putchar('\n');
return 0;
} void Z_algorithm(const char *const A, const char *const B, const int x, const int y, const bool pt) {
int p = 0, q = 1;
if (!pt) {
while ((q < x) && (A[q] == A[q + 1])) ++q;
nxt[p = 2] = q - 1;
q = std::max(q, 2);
} else {
while ((q <= x) && (q <= y) && (A[q] == B[q])) ++q;
p = 1;
qw(--q, ' ', true);
}
for (int i = pt ? 2 : 3, _ans; i <= x; ++i) {
int a = i - p + 1;
int len = nxt[a];
if ((i + len - 1) >= q) {
_ans = std::max(0, q - i + 1);
while ((q < x) && (_ans < y) && (A[q+1] == B[_ans+1])) {
++_ans; ++q;
}
q = std::max(p = i, q);
} else {
_ans = len;
}
if (pt) {
qw(_ans, ' ', true);
} else {
nxt[i] = _ans;
}
}
}

【字符串】 Z-algorithm的更多相关文章

  1. ExKMP(Z Algorithm) 讲解

    目录 问题引入 CaiOJ 1461 [EXKMP]最长共同前缀长度 算法讲解 匹配过程 next 的求解 复杂度证明 代码解决 一些例题 UOJ #5. [NOI2014]动物园 CF1051E V ...

  2. [leetcode]6. ZigZag Conversion字符串Z形排列

    The string "PAYPALISHIRING" is written in a zigzag pattern on a given number of rows like ...

  3. IE6/7/8中parseInt第一个参数为非法八进制字符串且第二个参数不传时返回值为0

    JavaScript中数字有十进制.八进制.十六进制.以"0"开头的是八进制,"0x"或"0X"开头的是十六进制. parseInt用来把字 ...

  4. Python小代码_7_字符串的字符次数统计

    生成包含 1000 个随机字符的字符串,并统计每个字符出现的次数. import string import random #获取字符 x = string.ascii_letters + strin ...

  5. Python:字符串操作总结

    所有标准的序列操作(索引.分片.乘法.判断成员资格.求长度.取最小值最大值)对字符串同样适用,且字符串是不可变的. 一.字符串格式化 转换说明符 [注]: 这些项的顺序至关重要 (1)%字符:标记转换 ...

  6. Sqlite数据库字符串处理函数replace

    Sqlite 字符串处理函数replace官方说明: replace(X,Y,Z) The replace(X,Y,Z) function returns a string formed by sub ...

  7. Python3简明教程(七)—— 字符串

    字符串是 Python 中最常用的数据类型.本节实验将会学习如何对 Python3 的字符串进行处理操作. 字符串的三种表示 可以通过几种不同的方式表示字符串.如单引号('...')或双引号(&quo ...

  8. java中String字符串

    一.定义String字符串 String字符串和char字符不同,char使用单引号,只能表示一个字符,字符串就是一段文本.String是个类.这个类使用final修饰,所以这个类是不可以继承扩充和修 ...

  9. leetcode 467. 环绕字符串中唯一的子字符串

    题目描述: 把字符串 s 看作是“abcdefghijklmnopqrstuvwxyz”的无限环绕字符串,所以 s 看起来是这样的:"...zabcdefghijklmnopqrstuvwx ...

  10. 后缀自动机----一种将字符串变成DAG的方法

    后缀自动机 (suffix automaton, SAM) 是一个能解决许多字符串相关问题的有力的数据结构.(否则我们也不会用它) 举几个例子,以下的字符串问题都可以在线性时间内通过 SAM 解决 1 ...

随机推荐

  1. 深入V8引擎-写在前面

    这一篇不打算讲技术,聊点别的吧,写这个的原因主要是看到了我博客园的签名,开始这个最终源码系列前想说点什么. 转行前端(达成) 入行1年vue源码(达成).webpack源码(半达成) 入行2年争取读通 ...

  2. 匿名方法是怎样演变到Lambda表达试过程

    一.  "Lambda 表达式"(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda ab ...

  3. C#将运算字符串直接转换成表达式且计算结果

    DataTable dt = new DataTable(); var Result= dt.Compute("1+2*3+2", "");//将运算字符串转换 ...

  4. PIE SDK打开自定义矢量数据

    1. 数据介绍 信息提取和解译的过程中,经常会生成一部分中间临时矢量数据,这些数据在执行完对应操作后就失去了存在的价值,针对这种情况,PIE增加了内存矢量数据集,来协助用户完成对自定义矢量数据的读取和 ...

  5. 浅聊几种主流Docker网络的实现原理

    原文:https://mp.weixin.qq.com/s/Jdxct8qHrBUtkUq-hnxSRw 参考:https://blog.csdn.net/yarntime/article/detai ...

  6. English--介词省略句型与总结

    English|介词省略句型与总结 本篇文章将会介绍介词的省略与整个语法内容的总结.小板凳都带上,准备开始了! 前言 目前所有的文章思想格式都是:知识+情感. 知识:对于所有的知识点的描述.力求不含任 ...

  7. rabbitMq 学习笔记(一)

    消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题 实现高性能,高可用,可伸缩和最终一致性架构. RabbitMQ 是采用 Erlang 语言实现 AMQP (Adva ...

  8. 解决ubuntu安装ssh服务无法打开解析包问题

    Windows下做Linux开发需要SSH强大功能的支持.安装SSH的过程会出现了很多问题,看完这篇文章可以让你少走些弯路,PS:折腾一下午的成果. Ubuntu的apt-get工具的牛逼之处简直无人 ...

  9. 浓缩版java8新特性

    目录 一.Lambda 1.定义/设计原因 2.结构 3.规则 4.使用 二.函数式接口 1.定义 2.设计原因 3.使用 三.方法引用 1.定义/设计原因 2.使用 四.接口的默认方法 1.定义 2 ...

  10. C++ OpenSSL 之四:CER转换为PEM

    1.等同于使用: openssl  x509 -in "cer_path" -inform DER -out "save_path" -outform PEM ...