对于个长度为 \(n\) 的字符串 \(s\)。定义 \(z[i]\) 表示 \(s\) 和 \(s[i,n-1]\)(即以 \(s[i]\) 开头的后缀)的最长公共前缀(LCP)的长度。\(z\) 被称为 \(s\) 的 Z 函数。这里注意,在 Z 函数中,\(z[0] = 0\),但是根据 LCP 的定义,\(z[0] = n\),具体应该为何值,根据题目意思来判断。本文更偏向根据 LCP 的定义来确定 \(z[0]\) 的值

演示

对于字符串 \(\texttt{aaaaaaaba}\),它的 Z 函数是这样的。

\[z(\texttt{aaaaaaaba}) = \left [9, 6, 5, 4, 3, 2, 1, 0, 1 \right ]
\]

过程

我们设现在 \(i + z[i] - 1\) 的最大值为 \(r\),得到这个最大值的 \(i\) 为 \(l\)。

假设我们现在要求 \(z[x]\),\(z[0, x - 1]\) 已经求出来了,现在,让我们分类讨论各种情况。

  • 当 \(x <= r\) 时

如图所示,

因为 \(s[l, r]\) 等于 \(s[0, r - l]\),所以 \(s[l, x] = s[0, x - l]\),对应到下图,就是绿色区域和黄色区域相同。

因此,\(z[x]\) 的取值可以参考 \(z[l - x]\)。

\(z[x]\) 可以直接等于 \(z[l - x]\) 吗?

显然是不行的,像下面的情况,灰色区域为 \(z[l - x]\) 的长度,但是,对于 \(x\),有一小段的灰色区域超出了红色区域,因此不保证这段灰色区域与前面灰色区域的对应位置相等,所以,我们正确的写法应该是 \(z[x] = \min \{z[l - x], r - x + 1 \}\),随后再暴力拓展。

  • 当 \(x > r\) 时

没有“前车之鉴”,我们直接进行暴力拓展即可。


代码中的 \(i\) 就是 \(x\)。

if (i <= r) {
z[i] = min(z[i - l], 1ll * r - i + 1);
}

暴力拓展 + 修改 \(l, r\)

注意要判断边界,同时判断 \(x + z[x] - 1\) 与 \(r\) 的大小更新 \(l, r\),相信你可以看懂这段代码。

while (i + z[i] < len and s[z[i]] == s[i + z[i]]) {
++ z[i];
}
if (i + z[i] - 1 > r) {
l = i;
r = i + z[i] - 1;
}

拼凑一下,就是 Z 函数(或者是扩展 KMP)的代码了。

void Z(char* s, ll* z) {
int len = strlen(s), l = 0, r = 0;
rep (i, 1, len - 1, 1) {
if (i <= r) {
z[i] = min(z[i - l], 1ll * r - i + 1);
}
while (i + z[i] < len and s[z[i]] == s[i + z[i]]) {
++ z[i];
}
if (i + z[i] - 1 > r) {
l = i;
r = i + z[i] - 1;
}
}
}

匹配所有子串

为了避免混淆,我们将 \(t\) 称作 文本,将 \(p\) 称作 模式。所给出的问题是:寻找在文本 \(t\) 中模式 \(p\) 的所有出现。

为了解决该问题,我们构造一个新的字符串 \(s = p + \diamond + t\),也即我们将 \(p\) 和 \(t\) 连接在一起,但是在中间放置了一个分割字符 \(\diamond\)(我们将如此选取 \(\diamond\) 使得其必定不出现在 \(p\) 和 \(t\) 中)。

首先计算 \(s\) 的 Z 函数。接下来,对于在区间 \([0,\left |t \right | - 1]\) 中的任意 \(i\),我们考虑以 \(t[i]\) 为开头的后缀在 \(s\) 中的 Z 函数值 \(k = z[i + \left |p \right | + 1]\)。如果 \(k = \left |p \right |\),那么我们知道有一个 \(p\) 的出现位于 \(t\) 的第 \(i\) 个位置,否则没有 \(p\) 的出现位于 \(t\) 的第 \(i\) 个位置。

其时间复杂度(同时也是其空间复杂度)为 \(O(\left |t \right | + \left |p \right |)\)。

// 匹配 A 在 B 中的所有出现
void Z(char* s, ll* z) {
int len = strlen(s), l = 0, r = 0;
rep (i, 1, len - 1, 1) {
if (i <= r) {
z[i] = min(z[i - l], 1ll * r - i + 1);
}
while (i + z[i] < len and s[z[i]] == s[i + z[i]]) {
++ z[i];
}
if (i + z[i] - 1 > r) {
l = i;
r = i + z[i] - 1;
}
}
} void get_ext() {
strcpy(p, b);
strcat(p, "#");
strcat(p, a);
Z(p, z);
}

P5410 【模板】扩展 KMP(Z 函数) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

//The code was written by yifan, and yifan is neutral!!!

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define bug puts("NOIP rp ++!");
#define rep(i, a, b, c) for (int i = (a); i <= (b); i += (c))
#define per(i, a, b, c) for (int i = (a); i >= (b); i -= (c)) template<typename T>
inline T read() {
T x = 0;
bool fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
} const int N = 2e7 + 5; ll z[N << 1];
char a[N], b[N], p[N << 1]; void input() {
scanf("%s", a);
scanf("%s", b);
} void Z(char* s, ll* z) {
int len = strlen(s), l = 0, r = 0;
rep (i, 1, len - 1, 1) {
if (i <= r) {
z[i] = min(z[i - l], 1ll * r - i + 1);
}
while (i + z[i] < len and s[z[i]] == s[i + z[i]]) {
++ z[i];
}
if (i + z[i] - 1 > r) {
l = i;
r = i + z[i] - 1;
}
}
} void get_ext() {
strcpy(p, b);
strcat(p, "#");
strcat(p, a);
Z(p, z);
} void solve() {
int lenz = strlen(b);
int lenext = strlen(p);
ll ans = 0;
z[0] = lenz;
rep (i, 0, lenz - 1, 1) {
ans = ans ^ ((i + 1) * (z[i] + 1));
}
cout << ans;
putchar('\n');
ans = 0;
rep (i, lenz + 1, lenext - 1, 1) {
ans = ans ^ ((i - lenz) * (z[i] + 1));
}
cout << ans;
putchar('\n');
} int main() {
input();
get_ext();
solve();
return 0;
}

字符串整周期

给定一个长度为 \(n\) 的字符串 \(s\),找到其最短的整周期,即寻找一个最短的字符串 \(t\),使得 \(s\) 可以被若干个 \(t\) 拼接而成的字符串表示。

考虑计算 \(s\) 的 Z 函数,则其整周期的长度为最小的 \(n\) 的因数 \(i\),满足 \(i+z[i]=n\)。

题目

P7114 [NOIP2020] 字符串匹配 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

求出每个位置的 Z 函数,通过判断 \((AB)\) 个数的奇偶来计算出现奇数次字符的个数,用树状数组维护。

//The code was written by yifan, and yifan is neutral!!!

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define bug puts("NOIP rp ++!");
#define rep(i, a, b, c) for (int i = (a); i <= (b); i += (c))
#define per(i, a, b, c) for (int i = (a); i >= (b); i -= (c))
#define lowbit(x) (x & (-x)) template<typename T>
inline T read() {
T x = 0;
bool fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
} const int N = 2e6 + 5; int T, n, all, prefix, suffix;
int pre[30], nxt[30], Z[N], t[30];
char s[N]; void input() {
scanf("%s", s);
} void exkmp() {
int l = 0, r = 0;
rep (i, 1, n - 1, 1) {
if (i <= r) {
Z[i] = min(Z[i - l], r - i + 1);
}
while (s[i + Z[i]] == s[Z[i]] and i + Z[i] < n) {
++ Z[i];
}
if (i + Z[i] - 1 > r) {
r = i + Z[i] - 1;
l = i;
}
}
Z[0] = n;
} void modify(int x) {
while (x <= 27) {
++ t[x];
x += lowbit(x);
}
} int query(int x) {
int ans = 0;
while (x) {
ans += t[x];
x -= lowbit(x);
}
return ans;
} void deal() {
n = strlen(s);
memset(pre, 0, sizeof pre);
memset(nxt, 0, sizeof nxt);
memset(Z, 0, sizeof Z);
memset(t, 0, sizeof t);
all = prefix = suffix = 0;
exkmp();
rep (i, 0, n - 1, 1) {
if (i + Z[i] == n) {
-- Z[i];
}
}
rep (i, 0, n - 1, 1) {
++ nxt[s[i] - 'a'];
}
rep (i, 0, 25, 1) {
if (nxt[i] & 1) {
++ all;
}
}
suffix = all;
ll ans = 0;
rep (i, 0, n - 1, 1) {
if (nxt[s[i] - 'a'] & 1) {
-- suffix;
} else {
++ suffix;
}
-- nxt[s[i] - 'a'];
if (pre[s[i] - 'a'] & 1) {
-- prefix;
} else {
++ prefix;
}
++ pre[s[i] - 'a'];
if (i != 0 && i != n - 1) {
int t = Z[i + 1] / (i + 1) + 1;
ans += 1ll * (t / 2) * query(all + 1) + 1ll * (t - t / 2) * query(suffix + 1);
}
modify(prefix + 1);
}
cout << ans << '\n';
} void solve() {
T = read<int>();
while (T --) {
input();
deal();
}
} int main() {
solve();
return 0;
}

「学习笔记」扩展 KMP(Z 函数)的更多相关文章

  1. 「学习笔记」字符串基础:Hash,KMP与Trie

    「学习笔记」字符串基础:Hash,KMP与Trie 点击查看目录 目录 「学习笔记」字符串基础:Hash,KMP与Trie Hash 算法 代码 KMP 算法 前置知识:\(\text{Border} ...

  2. 「学习笔记」FFT 快速傅里叶变换

    目录 「学习笔记」FFT 快速傅里叶变换 啥是 FFT 呀?它可以干什么? 必备芝士 点值表示 复数 傅立叶正变换 傅里叶逆变换 FFT 的代码实现 还会有的 NTT 和三模数 NTT... 「学习笔 ...

  3. 「学习笔记」Treap

    「学习笔记」Treap 前言 什么是 Treap ? 二叉搜索树 (Binary Search Tree/Binary Sort Tree/BST) 基础定义 查找元素 插入元素 删除元素 查找后继 ...

  4. 「学习笔记」Min25筛

    「学习笔记」Min25筛 前言 周指导今天模拟赛五分钟秒第一题,十分钟说第二题是 \(\text{Min25}​\) 筛板子题,要不是第三题出题人数据范围给错了,周指导十五分钟就 \(\text{AK ...

  5. 「学习笔记」FFT 之优化——NTT

    目录 「学习笔记」FFT 之优化--NTT 前言 引入 快速数论变换--NTT 一些引申问题及解决方法 三模数 NTT 拆系数 FFT (MTT) 「学习笔记」FFT 之优化--NTT 前言 \(NT ...

  6. luogu P5410 模板 扩展 KMP Z函数 模板

    LINK:P5410 模板 扩展 KMP Z 函数 画了10min学习了一下. 不算很难 思想就是利用前面的最长匹配来更新后面的东西. 复杂度是线性的 如果不要求线性可能直接上SA更舒服一点? 不管了 ...

  7. 「学习笔记」平衡树基础:Splay 和 Treap

    「学习笔记」平衡树基础:Splay 和 Treap 点击查看目录 目录 「学习笔记」平衡树基础:Splay 和 Treap 知识点 平衡树概述 Splay 旋转操作 Splay 操作 插入 \(x\) ...

  8. 「学习笔记」wqs二分/dp凸优化

    [学习笔记]wqs二分/DP凸优化 从一个经典问题谈起: 有一个长度为 \(n\) 的序列 \(a\),要求找出恰好 \(k\) 个不相交的连续子序列,使得这 \(k\) 个序列的和最大 \(1 \l ...

  9. 「学习笔记」ST表

    问题引入 先让我们看一个简单的问题,有N个元素,Q次操作,每次操作需要求出一段区间内的最大/小值. 这就是著名的RMQ问题. RMQ问题的解法有很多,如线段树.单调队列(某些情况下).ST表等.这里主 ...

  10. 「学习笔记」递推 & 递归

    引入 假设我们想计算 \(f(x) = x!\).除了简单的 for 循环,我们也可以使用递归. 递归是什么意思呢?我们可以把 \(f(x)\) 用 \(f(x - 1)\) 表示,即 \(f(x) ...

随机推荐

  1. 500行代码手写docker-以新命名空间运行程序

    (2)500行代码手写docker-以新命名空间运行程序 本系列教程主要是为了弄清楚容器化的原理,纸上得来终觉浅,绝知此事要躬行,理论始终不及动手实践来的深刻,所以这个系列会用go语言实现一个类似do ...

  2. Windows 10 v2022-L.1345 神州网信政府版

    Windows 10 v2022-L.1345 神州网信政府版 Windows 10 神州网信政府版(以下简称CMGE)V2022-L是在 Windows 10 21H2的基础上,根据中国有关法律.法 ...

  3. Windows系统中,如何快速找到端口被占用的进程?

    在本地调试代码时,经常遇到端口被占用导致启动失败的问题,又不能很快找到哪个进程占用了端口,很是恼火. 今天,我们用shell命令轻松搞定. 一.打开命令提示符 window+R 组合键,调出命令窗口. ...

  4. tryhackme-OWASP

    tryhackme-OWASP Top 10部分记录 敏感信息泄露 在assets目录中 可以看到到一个sqlite数据库的webapp.db文件 使用sqlite3 webapp.db .table ...

  5. [Qt开发]一口气搞懂串口通信

    好多小鳄鱼 一.关于串口通信: Qt的确有自己的串口通信类,就是QSerialPort,但是我们在使用过程中因为要更加定制化的使用串口通信类减小开发的难度,所以我们会提供一个串口通信类,也就是这个Se ...

  6. SpringBoot之MVC配置(WebMvcConfigurer详解)

    一:基本介绍 Spring MVC是一种常用的Java Web框架,它提供了一种基于MVC模式的开发方式,可以方便地实现Web应用程序.在Spring MVC中,WebMvcConfigurer是一种 ...

  7. 01-面试必会-JAVA基础篇

    1. Final 有什么用? 展开查看 被 final 修饰的类不可以被继承 被 final 修饰的方法不可以被重写 被 final 修饰的变量不可以被改变, 被 final 修饰不可变的是变量的引用 ...

  8. Fabric架构详解

    1 整体架构 2 运行架构 Fabric CA(可选) peer:主节点模块,负责存储区块链数据,运行维护链码 orderer:交易打包,排序模块 cryptogen:组织和证书等资料生成模块 con ...

  9. knn和线性分类器

    一.knn算法概述 knn首选是最简单的分类算法,其是有监督学习的分类算法之一. 二.knn算法过程 knn(k nearest neighbors k个最近的邻居):knn是当预测一个新的值x的时候 ...

  10. XTTS系列之四:迷迷糊糊的并行度

    项目测试组又反馈一个问题,XTTS执行全量备份速度慢,影响测试进度. 实际算了下,平均速度才150MB/s.. 这个速度在客户生产环境的确是不够看,首先询问是否开了并行,开了多少? 回复是说有开32个 ...