manacher 算法

manacher 算法

\(\mathcal O(n)\) 求有关回文的字符串算法。

定义 \([l, r]\) 表示字符串下标 \(l\)\(r\) 形成的子串。

流程

  1. 将原字符串首、尾、中间分别插入奇怪字符(如 #,目的是计算中心点在两个字符之间的回文串),再在最前端插入另一奇怪字符串(如 @,目的是防止数组越界),eg. abcb 变为 @#a#b#c#b#
  2. 对每个位置记录 \(p_i\) 表示在当前字符串以 \(i\) 为回文中心的最大回文串为 \([i - p_i + 1, i + p_i - 1]\)
  3. 考虑从前往后做,边做边记录两个值,分别为 \(id, maxright\) 分别表示当前所有回文串中,右端点最大的回文串对应的回文中心以及右端点。显然这个可以边做边维护。
  4. 考虑如何求 \(p_i\),如果说 \(maxright > i\),那么 \(i\) 关于 \(id\) 对称的 \(j(i + j = 2 \times id \implies j = 2 \times id - i)\) 的信息我们是可以用到的(若 \(i + p_j - 1\) 超出 \(maxright\),则超出的那个部分不可取)。具体来说,如果 \(maxright > i\)\(p_i = \min(maxright - i+1, p_j)\),否则 \(p_i = 0\)
  5. 不断扩大 \(p_i\),直到求出 \(p_i\)

时间复杂度证明

  1. \(p_i = maxright - i + 1\),则 \(maxright\) 随着 \(p_i\) 增大而增大。
  2. \(p_i = p_j\),则 \(p_i\) 不会再继续扩展。
  3. \(p_i = 0\),则 \(maxright\) 随着 \(p_i\) 增大而增大。

故,\(maxright\) 均会随着 \(p_i\) 增大而增大,又考虑到 \(maxright\) 最多增大 \(n\) 次,故时间复杂度为 \(\mathcal O(n)\)

回文串大小

根据 \(p_i\) 的定义,其实对应到原串上,以 \(i\) 为对称中心的最大的回文串长度为 \(p_i-1\)(可以分情况讨论,这里不展开)。

题目链接

#include <bits/stdc++.h>
const int N = 1.1e7 + 10;
int len, lenr;
char s[N], r[N * 2];
int p[N * 2], ans;
inline void manacher()
{
    int id = 0, maxright = 0;
    for (int i = 1; i <= lenr; ++ i)
    {
        if (maxright > i)
            p[i] = std::min(maxright - i + 1, p[id * 2 - i]);
        else p[i] = 0;
        while (r[i + p[i]] == r[i - p[i]])
            p[i] ++;
        if (i + p[i] - 1 > maxright)
        {
            id = i;
            maxright = i + p[i] - 1;
        }
        ans = std::max(ans, (p[i] - 1));
    }
}

int main()
{
    scanf("%s", s + 1);
    len = strlen(s + 1);
    r[0] = '@';
    for (int i = 1; i <= len; ++ i)
        r[i * 2 - 1] = '#', r[i * 2] = s[i];
    r[len * 2 + 1] = '#';
    lenr = len * 2 + 1;

    manacher();

    printf("%d\n", ans);
    return 0;
}
posted @ 2022-03-18 00:08  chzhc  阅读(43)  评论(0编辑  收藏  举报
levels of contents