对于个长度为 \(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. Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535.

    问题描述 新建表或者修改表varchar字段长度的时候,出现这个错误 Row size too large. The maximum row size for the used table type, ...

  2. ChatGPT4通道开放接入基于OPEN AI 平台你的任何APP 可一键接入AI 智能

    你一定很好奇什么是 OPEN AI快速开发平台 顾名思义,开放的OPEN AI平台. 基于这个平台你的上层应用,如何 APP,小程序,H5,WEB, 公众号,任何一切终端都可以轻松接入,AI智能应用. ...

  3. git从配置到使用

    一 .安装git 1.1 官方地址为:https://git-scm.com/download/win 1.2 双击下载的.exe文件 1.3 直接下一步 1.4 自定义安装目录 1.5 勾选命令行和 ...

  4. HA高可用集群部署

    HA高可用集群部署 高可用 ZooKeeper 集群部署 zookeeper安装部署 注意:需要安装jdk,但jdk已经在第4章装过,这里直接装zookeeper #解压并安装zookeeper [r ...

  5. Kubernetes(k8s)服务service:service的发现和service的发布

    目录 一.系统环境 二.前言 三.Kubernetes service简介 四.使用hostPort向外界暴露应用程序 4.1 创建deploy 4.2 使用hostPort向外界暴露pod的端口 五 ...

  6. GIS融合之路(三)CesiumJS和ThreeJS相机同步

    同样在这篇文章开始前重申一下,山海鲸并没有使用ThreeJS引擎.但由于ThreeJS引擎使用广泛,下文中直接用ThreeJS同CesiumJS的整合方案代替山海鲸中3D引擎和CesiumJS整合. ...

  7. jQuery控制图片墙自动+手动淡入淡出切换

    先来看一下效果:http://39.105.101.122/myhtml/Jquery/img_switch/img_switch.html(甄嬛的眼睛有木有变大) 添加一个div(class=con ...

  8. 自然语言处理(NaturalLanguageProcessing,NLP)领域的100篇热门博客文章标题如下:

    目录 文章标题:<自然语言处理(Natural Language Processing,NLP)领域的100篇热门博客文章标题如下> 背景介绍: 随着人工智能技术的不断发展和普及,自然语言 ...

  9. 行行AI人才直播第4期: 跟随占冰强老师走近《如何定制企业专属AI大模型?》

    行行AI人才是博客园和顺顺智慧共同运营的AI行业人才全生命周期服务平台. 每个企业定制专属AI大模型的目的都不同,比如某企业希望通过AI技术提升其客户服务和销售效果.该企业面临着庞大的商品数据.用户评 ...

  10. python笔记:第三章使用字符串

    1.1 字符串的基本操作 对序列的操作都适用于字符串,但字符串是不可变的,所以元素赋值和切片赋值都是非法的 1.2 设置字符串的格式 方法一: 使用%来设置字符串 format = 'Hello, % ...