$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)|(ackslash 0)|

(2.1.1) (str) 字符串

(str) 表示 (s) 处理后的串

为了避免对回文串长度奇偶性的讨论

考虑在 (s) 的首尾、以及中间每两个字符中插入分隔符 ($)

并且在 (str) 首部位置插入一个哨兵 ($)

(str) 具体的构造如下

|(0)|(1)|(2)|(3)|(4)|(5)|(6)|(7)|(8)|(9)|(10)|(11)|(12)|
|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
|($)|($)|(a)|($)|(b)|($)|(c)|($)|(b)|($)|(a)|$|(ackslash 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] = '';

(2.1.2) (pleft[i ight]) 数组

(p[i]) 表示每个位置 (i) 可以扩展的最大长度,也等于以位置 (i) 为回文中心的回文串长度

构造如下

|(0)|(1)|(2)|(3)|(4)|(5)|(6)|(7)|(8)|(9)|(10)|(11)|(12)|
|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
|($)|($)|(a)|($)|(b)|($)|(c)|($)|(b)|($)|(a)|($)|(ackslash 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] = minleft{p[j], maxr - i ight} ]

(2.2.2) (igeq 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], " 
"[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] = '';
    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], " 
"[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] = '';
    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;
}
原文地址:https://www.cnblogs.com/ChenyangXu/p/12907443.html