字符串 Manacher

字符串 Manacher

Manacher算法应用于特殊场景:查找一个长度为 \(n\) 的字符串 \(S\) 中最长回文子串(\(\text {Longest Palindromic Substring, LPS}\))。

算法的复杂度为 \(O(n)\),是这个场景中效率最高的回文串算法。

一、改造字符串

给定一个字符串 \(S\),在每个字符左右插入一个不属于 \(S\) 的字符,如 #,最左右端加上“哨兵”,如&,防止越界。

例如 \(S=a\ b\ c\ b\ a\),改造后得 \(S^{\prime}=\&\ \#\ a\ \#\ b\ \#\ c\ \#\ b\ \#\ a\ \#\ \&\)\(S\) 长度为5,\(S^{\prime}\) 长度为13。

\(S=a\ b\ b\ a\),改造后得 \(S^{\prime}=\&\ \#\ a\ \#\ b\ \ \#\ b\ \#\ a\ \#\ \&\)\(S\) 长度为4,\(S^{\prime}\) 长度为11。

可以发现,无论原字符串 \(S\) 长度是否为奇数还是偶数,改造后的字符串 \(S^{\prime}\) 长度都是奇数

这就保证了字符串的中心字符只有一个。

二、回文半径

求回文半径的过程是动态规划的思路,所以Manacher算法是一种动态规划算法。

定义数组 \(P[\ ]\)\(P[\ i\ ]\) 是以字符 \(S[\ i\ ]\) 为中心字符的最长回文串的半径。

那么暴力中心扩展法时,利用 S[i - P[i]] == S[i + P[i]] 即可判断是否为回文串。

假设已经计算出了 \(P[\ 0\ ]\sim P[\ i - 1\ ]\)
R\(P[\ 0\ ]\sim P[\ i - 1\ ]\) 这些回文串中最大的右端点, C 是这个 R 对应的回文串的中心点。可得 R = C + P[C]
所以反复比对 R = i + P[i] 选出最大值,就能找到 RC

注意:R 右侧字符处于未查询的状态。

接下来计算 \(P[\ i\ ]\)

1)\(i\ge R\) ,由于 R 右侧字符未查询过,所以只能将 \(P[\ i \ ]\) 初始化为1,即 P[i] = 1

2)\(i<R\) ,可以再分为两种情况:

​ 设 \(j\)C 左侧的一个与 \(i\) 镜像的字符,且 \(P[\ j \ ]\) 已知。

  • \(j\) 的回文串被 C 的回文串包含,那么一定会有 \(P[\ i\ ] = P[\ j\ ]\)(镜像原理)。
    因为 \((i+j)\ /\ 2=C\),所以 \(P[\ i\ ]=P[\ j\ ]=P[2C-i]\),这里可以写成 P[i] = P[(C << 1) - i]
    剩下的继续用暴力中心扩展法完成 \(P[\ i\ ]\) 的计算。

  • \(j\) 的回文串不被 C 的回文串包含,即 \(j\) 的回文串左端小于 C 回文串的左端。
    由于镜像原理,这就说明 \(i\) 回文串的右端超出 R 范围,而 R 右侧未查询过。
    所以,只能预先设定 \(P[\ i\ ] = R-i\),即最右端 R\(i\) 的距离。
    R = C + P[C] 可得,P[i] = C + P[C] - i

P3805 【模板】manacher - 洛谷

题目描述

给出一个只由小写英文字符 \(\texttt a,\texttt b,\texttt c,\ldots\texttt y,\texttt z\) 组成的字符串 \(S\) ,求 \(S\) 中最长回文串的长度 。

字符串长度为 \(n\)

输入格式

一行小写英文字符 \(\texttt a,\texttt b,\texttt c,\cdots,\texttt y,\texttt z\) 组成的字符串 \(S\)

输出格式

一个整数表示答案。

输入输出样例 #1

输入 #1

aaa

输出 #1

3

说明/提示

\(1\le n\le 1.1\times 10^7\)

代码

#include <bits/stdc++.h>
using namespace std;
const int N = 11000002;
int n, P[N << 1];
char a[N], S[N << 1];

void change()
{
    n = strlen(a);
    int k = 0; S[k ++] = '$', S[k ++] = '#';
    for (int i = 0; i < n; i++) {
        S[k ++] = a[i];
        S[k ++] = '#';
    }
    S[k ++] = '&';
    n = k;
}

void manacher()
{
    int R = 0, C;
    for (int i = 1; i < n; i++) {
        if (i < R) P[i] = min(P[(C << 1) - i], P[C] + C - i);
        else P[i] = 1;
        while (S[i + P[i]] == S[i - P[i]]) P[i]++;
        if (P[i] + i > R) {
            R = P[i] + i;
            C = i;
        }
    }
}

int main()
{
    cin >> a;
    change();
    manacher();
    int ans = 1;
    for (int i = 0; i < n; i++) ans = max(ans, P[i]);
    cout << ans - 1;

    return 0;
}
posted @ 2025-03-09 00:54  AKgrid  阅读(35)  评论(0)    收藏  举报