\(Manacher\) 算法
\(1.\) 算法概述
给定字符串 \(s\),可以在 \(O(n)\) 时间内处理出以每个位置 \(i\) 为回文中心所具有的回文串的长度,进而可以在 \(O(n)\) 时间内求出 \(s\) 的最大回文子串长度
\(2.\) 算法详解
\(2.1\) 引入参数
设 \(s = abcba\) 如下
\(0\) | \(1\) | \(2\) | \(3\) | \(4\) | \(5\) | \(6\) |
---|---|---|---|---|---|---|
\(a\) | \(b\) | \(c\) | \(b\) | \(a\) | \(\backslash 0\) |
\(2.1.1\) \(str\) 字符串
\(str\) 表示 \(s\) 处理后的串
则对应的 \(str\) 构造如下
\(0\) | \(1\) | \(2\) | \(3\) | \(4\) | \(5\) | \(6\) | \(7\) | \(8\) | \(9\) | \(10\) | \(11\) | \(12\) |
---|---|---|---|---|---|---|---|---|---|---|---|---|
\(\$\) | \(\$\) | \(a\) | \(\$\) | \(b\) | \(\$\) | \(c\) | \(\$\) | \(b\) | \(\$\) | \(a\) | $ | \(\backslash 0\) |
对应的代码如下
int L = strlen(s + 1);
for(int i = 1; i <= L; ++i)
str[2 * i] = s[i], str[2 * i + 1] = '$';
L = L * 2 + 1, str[0] = str[1] = '$', str[L + 1] = '\0';
\(2.1.2\) \(p\left[i\right]\) 数组
\(p[i]\) 表示每个位置 \(i\) 可以扩展的最大长度,也等于以位置 \(i\) 为回文中心的回文串长度
构造如下
\(0\) | \(1\) | \(2\) | \(3\) | \(4\) | \(5\) | \(6\) | \(7\) | \(8\) | \(9\) | \(10\) | \(11\) | \(12\) |
---|---|---|---|---|---|---|---|---|---|---|---|---|
\(\$\) | \(\$\) | \(a\) | \(\$\) | \(b\) | \(\$\) | \(c\) | \(\$\) | \(b\) | \(\$\) | \(a\) | \(\$\) | \(\backslash 0\) |
\(0\) | \(1\) | \(0\) | \(1\) | \(0\) | \(5\) | \(0\) | \(1\) | \(0\) | \(1\) | \(0\) |
\(2.1.3\ mid,\ maxr\)
\(maxr\) 表示当前所有回文子串中的最右端点,\(mid\) 表示这个回文子串的回文中心
\(2.2\) 构造 \(p[i]\)
\(manacher\) 算法复杂度之所以优秀,关键在于 \(p[i]\) 的构造
基本的思想在于利用已知的 \(maxr,\ mid\) 来计算 \(p[i]\),具体可以分成两类
\(2.2.1\) \(i < maxr\)
图源洛谷,\(pos\) 即 \(mid\),示例如下
设 \(j\) 是 \(i\) 关于 \(mid\) 的对称点
由对称性可知,填色的两个回文串一模一样,所以 \(p[i] = p[j]\)
但若回文长度过长,超出了 \(maxl/\ maxr\),如下所示
那么 \(p[i] = j - maxl = maxr - i\)
所以取二者最小即可
\(2.2.2\) \(i\geq maxr\)
只能暴力拓展
具体代码如下
for(int i = 1; i <= L; ++i) {
if(i < maxr) p[i] = min(p[2 * mid - i], maxr - i);
while(str[i - p[i] - 1] == str[i + p[i] + 1]) ++p[i]; //暴力拓展
if(i + p[i] > maxr) maxr = i + p[i], mid = i; //更新 maxr, mid
}
\(2.3\) 算法复杂度分析
每次暴力拓展时,更新 \(maxr\)
而 \(maxr\) 一直在右移,移动次数总计 \(O(n)\) 次
加上一个遍历的复杂度 \(O(n)\)
故总的复杂度仍然为 \(O(n)\)
\(3.\) 代码模板
#include <bits/stdc++.h>
#define fi first
#define se second
#define pii pair<int, int>
#define pb push_back
#define arrayDebug(a, l, r) for(int i = l; i <= r; ++i) printf("%d%c", a[i], " \n"[i == r])
typedef long long LL;
typedef unsigned long long ULL;
const LL INF = 0x3f3f3f3f3f3f3f3f;
const LL MOD = 11092019;
const int inf = 0x3f3f3f3f;
const int DX[] = {0, -1, 0, 1, 0, -1, -1, 1, 1};
const int DY[] = {0, 0, 1, 0, -1, -1, 1, 1, -1};
const int N = 4e7 + 7;
const double PI = acos(-1);
const double EPS = 1e-6;
using namespace std;
inline int read()
{
char c = getchar();
int ans = 0, f = 1;
while(!isdigit(c)) {if(c == '-') f = -1; c = getchar();}
while(isdigit(c)) {ans = ans * 10 + c - '0'; c = getchar();}
return ans * f;
}
char s[N], str[N];
int L, p[N];
void manacher()
{
for(int i = 1; i <= L; ++i) str[2 * i] = s[i], str[2 * i + 1] = '$';
L = L * 2 + 1, str[0] = str[1] = '$', str[L + 1] = '\0';
int mid = 0, maxr = 0;
for(int i = 1; i <= L; ++i) {
if(i < maxr) p[i] = min(p[2 * mid - i], maxr - i);
while(str[i - p[i] - 1] == str[i + p[i] + 1]) ++p[i];
if(i + p[i] > maxr) maxr = i + p[i], mid = i;
}
}
int main()
{
scanf("%s", s + 1);
L = strlen(s + 1);
manacher();
int ans = 0;
for(int i = 1; i <= L; ++i) ans = max(ans, p[i]);
cout<<ans<<endl;
return 0;
}
\(4.\) 应用
\(4.1\) 求最长回文前缀
用 \(manacher\) 预处理 \(p[i]\) 数组
对于新字符串 \(str\) 的每一个位置 \(i\),判断其回文半径是否触及左端点 \(1\),即 \(i - p[i] = 1\),若可以触及,说明是一个回文前缀,更新长度即可
代码如下
#include <bits/stdc++.h>
#define fi first
#define se second
#define pii pair<int, int>
#define pb push_back
#define arrayDebug(a, l, r) for(int i = l; i <= r; ++i) printf("%d%c", a[i], " \n"[i == r])
typedef long long LL;
typedef unsigned long long ULL;
const LL INF = 0x3f3f3f3f3f3f3f3f;
const LL MOD = 11092019;
const int inf = 0x3f3f3f3f;
const int DX[] = {0, -1, 0, 1, 0, -1, -1, 1, 1};
const int DY[] = {0, 0, 1, 0, -1, -1, 1, 1, -1};
const int N = 4e7 + 7;
const double PI = acos(-1);
const double EPS = 1e-6;
using namespace std;
inline int read()
{
char c = getchar();
int ans = 0, f = 1;
while(!isdigit(c)) {if(c == '-') f = -1; c = getchar();}
while(isdigit(c)) {ans = ans * 10 + c - '0'; c = getchar();}
return ans * f;
}
char s[N], str[N];
int L, p[N];
void manacher()
{
for(int i = 1; i <= L; ++i) str[2 * i] = s[i], str[2 * i + 1] = '$';
L = L * 2 + 1, str[0] = str[1] = '$', str[L + 1] = '\0';
int mid = 0, maxr = 0;
for(int i = 1; i <= L; ++i) {
if(i < maxr) p[i] = min(p[2 * mid - i], maxr - i);
while(str[i - p[i] - 1] == str[i + p[i] + 1]) ++p[i];
if(i + p[i] > maxr) maxr = i + p[i], mid = i;
}
}
int main()
{
scanf("%s", s + 1);
L = strlen(s + 1);
manacher();
int ans = 0;
for(int i = 1; i <= L; ++i)
if(i - p[i] == 1) ans = max(ans, p[i]);
cout<<ans<<endl;
return 0;
}