扩展KMP学习笔记

前置芝士: KMP, manachar

告示:本文字符串下标均从 $1$ 开始。

扩展KMP算法提供了一个计算 $Z$ 函数的方法。

求解 $Z$ 函数
定义 $Z$ 函数 $z_i$ 表示字符串 $s$ 以下标 $i$ 为开头的后缀与 $s$ 的最长公共前缀。

根据定义, $z_1 = n$, $n$ 为 $s$ 的长度。


如果直接暴力求解,显然每计算一次 $z_i$ 都要花费 $n$ 的时间,总时间是 $O(n^2)$。

不过可以通过扩展KMP算法,将此优化到 $O(n)$。

假设我们计算 $z_i$ 时, $1 \leq j < i$ 的 $z_j$ 已经全部算出。

我们可以维护一个 $r$ 最大的能与 $s$ 前缀匹配的$s[l:r]$。(这句话好好理解)

对于 $i \leq r$ 直接将 $z_i$ 初始成 $min(z_{i-l+1}, r-i+1)$, 否则为 $0$。

然后暴力往后匹配,求出 $z_i$, 更新之前的 $l,r$。

因为匹配次数跟 $r$ 相同, 所以是 $O(n)$ 的。

CODE

inline void getZ(char *s, int n) {
    for (int i = 1; i <= n; ++ i) z[i] = 0;
    z[1] = n;
    for (int i = 2, l = 0, r = 0; i <= n; ++ i) {
        if (i <= r) z[i] = min(z[i-l+1], r-i+1);
        while (i + z[i] <= n && s[i+z[i]] == s[z[i]+1]) ++ z[i];
        if (i + z[i] - 1 > r) l = i, r = i + z[i] - 1;
    }
}
View Code

求解文本串的后缀与模式串的匹配

根据上文, 我们可以先求出模式串的 $z$ 函数。

接着类似于 KMP, 我们对文本串开始对模式串进行匹配,发现计算方式与 $z$ 函数类似,于是只要改一下就行了。

CODE

inline void exkmp(char *s, int n, char *t, int m) {
    getZ(t, m);
    for (int i = 1; i <= n; ++ i) p[i] = 0;
    for (int i = 1, l = 0, r = 0; i <= n; ++ i) {
        if (i <= r) p[i] = min(z[i-l+1], r-i+1);
        while (i + p[i] <= n && s[i+p[i]] == t[p[i]+1]) ++ p[i];
        if (i + p[i] - 1 > r) l = i, r = i + p[i] - 1;
    }
}
View Code

 

posted @ 2023-01-09 14:08  LikC1606  阅读(39)  评论(0)    收藏  举报