$Manacher$ 算法学习笔记

\(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\)

所以取二者最小即可

\[p[i] = min\left\{p[j],\ maxr - i\right\} \]

\(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;
}

猜你喜欢

转载自www.cnblogs.com/ChenyangXu/p/12907443.html