Z函数(扩展kmp) & Manacher

Z函数

定义

(后文中字符串下标默认从0开始)

Z函数也可以认为是Z数组,即定义数组 \(z[i]\) 表示字符串 \(s\) 与其中以 \(s[i]\) 为开头的最长公共前缀

形式化的说,即 \(z[i]\) 表示为最大的 \(z[i]\) 满足 \(s[0 ...z[i] - 1] = s[i...i + z[i] - 1]\)

求解

这里直接讲解 \(o(n)\) 解法

我们注意到 \(z\) 数组是能像 \(kmp\) 中的 \(next\) 数组一致可重复贡献的

注意到 \(s[0...z[i] - 1]\)\(s[i...i + z[i] - 1]\) 是等价的,所以在此范围内\(z\) 数组也是等价的

那么我们就可以考虑利用左边贡献右边的方式

假设此时 \(z[0 ...i - 1]\) 已经全部求解完成,我们得到这其中影响范围最右的 \(z[j]\) ,并定义 \(r = j + z[j] - 1,l = j\)

根据我们上面的发现,区间 \([l, r]\) 与区间 \([0,l - 1]\) 是等价的

那么对于我们当前要求解的 \(i\) ,如果有 \(i < r\) ,那么我们可以得到 \(z[i] = z[i - l]\)

但注意,上面的等价性质是仅在此范围内成立的,所以如果 \(z[i - l] + i > r\) 则我们无法保证其还是等价的

所以我们在计算 \(z[i]\) 时应当注意上界,即 \(z[i] = min(z[i - 1],r - i + 1)\)

\(i >= r\) ,则我么无法快速求出 \(z[i]\) ,这时暴力求出 \(z[i]\) 即可

示例代码

void Z(string s) {
    int n = s.length();
    int l = 0, r = 0; 
    for (int i = 1; i < n; ++i) {
        if (i <= r) {
            z[i] = min(r - i + 1, z[i - l]);
        }
        while (i + z[i] < n && s[z[i]] == s[i + z[i]]) {
            z[i]++;
        }
        if (i + z[i] - 1 > r) {
            l = i;
            r = i + z[i] - 1;
        }
    }
    return z;
}

时间复杂度

我们可以分析 \(r\) 来证明时间复杂度

首先 \(r\) 是单调的,且 \(r\) 的上界为 \(n\) ,并且由于 \(i\) 单调递增且固定 $ +1$ ,而 \(r\) 又一定大于等于 \(i\) ,所以 \(r\) 会在 \(O(n)\) 的时间内到达上界

到达上界之后就不会再进行暴力扩展的操作,所以总时间复杂度也是 \(O(n)\)


Manacher(马拉车)

定义

\(Manacher\) 算法,又称为马拉车算法,是一种求解回文字串问题的算法,它可以在 \(O(n)\) 的时间内求出每个位置的最长回文半径

算法过程

在这里,我们只考虑计算奇数长度的回文串,因为偶数长度的回文串我们可以通过添加间隔符来变成奇数长度

定义 \(p[i]\) 表示在字符串 \(s\) 当中,以 \(i\) 为中心点的最长回文半径

我们注意到回文串性质是左串翻转后等于右串,并且又因为我们考虑的是回文串,所以我们并不在意子串顺序

所以我们就有了结论,即左右字串的 \(p[i]\)此范围内是等价的

所以我们就可以用左子串贡献右子串的方式

\(r = max(j +p[j] - 1), c = j\)

对于当前要求解的 \(i\) ,若 \(r > i\) ,则 \(p[i] = min(2 * c - i,r - i + 1)\) 这里取 \(min\) 是为了防止跳出范围

否则暴力求解 \(p[i]\) ,之后不断更新 \(r, c\) 即可

到这里,阅读完上面Z函数之后可能就已经发现,\(Manacher\) 算法本质与Z函数相同

仅仅是在贡献内容上有区别,所以代码也基本一致,故时间复杂度证明也不再赘述

示例代码

void Manacher(string s) {
    int n = s.length();
    int c = 0, r = 0; 
    for (int i = 1; i < n; ++i) {
        if (i <= r) {
            z[i] = min(p[2 * c - i], r - i + 1);
        }
        while (s[i + p[i]] == s[i - p[i]]) {
            p[i]++;
        }
        if (i + p[i] - 1 > r) {
            c = i;
            r = i + p[i] - 1;
        }
    }
    return z;
}
posted @ 2026-08-05 15:46  Accepted_wyr  阅读(22)  评论(0)    收藏  举报