字符串 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]
选出最大值,就能找到 R
和 C
。
注意: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;
}