「学习笔记」扩展 KMP(Z 函数)

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

演示#

对于字符串 aaaaaaaba,它的 Z 函数是这样的。

z(aaaaaaaba)=[9,6,5,4,3,2,1,0,1]

过程#

我们设现在 i+z[i]1 的最大值为 r,得到这个最大值的 il

image

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

  • xr#

如图所示,

image

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

image

因此,z[x] 的取值可以参考 z[lx]

z[x] 可以直接等于 z[lx] 吗?

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

image

  • x>r#

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


代码中的 i 就是 x

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

暴力拓展 + 修改 l,r#

注意要判断边界,同时判断 x+z[x]1r 的大小更新 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++t,也即我们将 pt 连接在一起,但是在中间放置了一个分割字符 (我们将如此选取 使得其必定不出现在 pt 中)。

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

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

// 匹配 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;
}

作者:yifan0305

出处:https://www.cnblogs.com/yifan0305/p/17647143.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

转载时还请标明出处哟!

posted @   yi_fan0305  阅读(109)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示