「学习笔记」扩展 KMP(Z 函数)
对于个长度为 的字符串 。定义 表示 和 (即以 开头的后缀)的最长公共前缀(LCP)的长度。 被称为 的 Z 函数。这里注意,在 Z 函数中,,但是根据 LCP 的定义,,具体应该为何值,根据题目意思来判断。本文更偏向根据 LCP 的定义来确定 的值
演示#
对于字符串 ,它的 Z 函数是这样的。
过程#
我们设现在 的最大值为 ,得到这个最大值的 为 。
假设我们现在要求 , 已经求出来了,现在,让我们分类讨论各种情况。
-
当 时#
如图所示,
因为 等于 ,所以 ,对应到下图,就是绿色区域和黄色区域相同。
因此, 的取值可以参考 。
可以直接等于 吗?
显然是不行的,像下面的情况,灰色区域为 的长度,但是,对于 ,有一小段的灰色区域超出了红色区域,因此不保证这段灰色区域与前面灰色区域的对应位置相等,所以,我们正确的写法应该是 ,随后再暴力拓展。
-
当 时#
没有“前车之鉴”,我们直接进行暴力拓展即可。
代码中的 就是 。
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;
}
拼凑一下,就是 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;
}
}
}
匹配所有子串#
为了避免混淆,我们将 称作 文本,将 称作 模式。所给出的问题是:寻找在文本 中模式 的所有出现。
为了解决该问题,我们构造一个新的字符串 ,也即我们将 和 连接在一起,但是在中间放置了一个分割字符 (我们将如此选取 使得其必定不出现在 和 中)。
首先计算 的 Z 函数。接下来,对于在区间 中的任意 ,我们考虑以 为开头的后缀在 中的 Z 函数值 。如果 ,那么我们知道有一个 的出现位于 的第 个位置,否则没有 的出现位于 的第 个位置。
其时间复杂度(同时也是其空间复杂度)为 。
// 匹配 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;
}
字符串整周期#
给定一个长度为 的字符串 ,找到其最短的整周期,即寻找一个最短的字符串 ,使得 可以被若干个 拼接而成的字符串表示。
考虑计算 的 Z 函数,则其整周期的长度为最小的 的因数 ,满足 。
题目#
P7114 [NOIP2020] 字符串匹配 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
求出每个位置的 Z 函数,通过判断 个数的奇偶来计算出现奇数次字符的个数,用树状数组维护。
//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;
}
作者:yifan0305
出处:https://www.cnblogs.com/yifan0305/p/17647143.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
转载时还请标明出处哟!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!