Manacher’s Algorithm \texttt{Manacher's Algorithm} Manacher’s Algorithm 是用来查找一个字符串的最长回文子串的线性方法,由一个叫 Manacher \texttt{Manacher} Manacher 的人在 1975 \texttt{1975} 1975 年发明的,广大 OIer \texttt{OIer} OIer都叫他马拉车算法。
当然还有些人管她叫没马拉车比如说wxd
查找回文串最简单最暴力的方法,是用 O ( n 3 ) \tt O({n}^{3}) O(n3) 的方法进行暴力枚举
一重循环用于枚举中心点,一重循环用于枚举半径,一重用于判断是不是回文串。
后来,人们改进了以上的算法,可以保持在 O ( n 2 ) \tt O(n^2) O(n2) 的复杂度之内。
一重用于枚举中心点,一重用于从小到大枚举半径,随便在枚举过程中判断回文串。
最后, Manacher \texttt{Manacher} Manacher 算法被发明了,用 O ( n ) \tt O(n) O(n) 的复杂度算出回文子串。
前置知识
无(字符串?)
算法用途
用于查找一个字符串的最长回文子串
算法复杂度
时间
O ( l e n ) \tt O(len) O(len)
空间
O ( l e n ) \tt O(len) O(len)
算法实现
回文串有个比较烦的情况,他的中心点可能在两个字符之间,也可能在一个字符上。
Manachar \texttt{Manachar} Manachar 对此的优化是在两个字符之间增加另一种字符,比如说:
1 2 3 4 5 6 7 8 \tt\kern 0.525em 1\kern 0.525em 2\kern 0.525em 3\kern 0.525em 4\kern 0.525em 5\kern 0.525em 6\kern 0.525em 7\kern 0.525em 8 12345678
# 1 # 2 # 3 # 4 # 5 # 6 # 7 # 8 # \tt\#1\#2\#3\#4\#5\#6\#7\#8\# #1#2#3#4#5#6#7#8#
这样,就可以把任意长度的字符串的长度转换为奇数了。
我们定义一个数组, P \tt P P, P [ i ] \tt{P[i]} P[i] 代表以第 i \tt i i 个字符为中心点的子回文串的半径。
s t r : # a # b # a # c # a # b # a # b # b # \tt str:\#a\#b\#a\#c\#a\#b\#a\#b\#b\# str:#a#b#a#c#a#b#a#b#b#
P : 1214121812141412321 \tt \kern 0.525em\kern 0.525emP:1214121812141412321 P:1214121812141412321
问题来了,我们只知道这个回文串的半径长度,还需要知道他起始位置才能确定这个回文串的位置。
我们虽然不知道他的起始位置,我们知道他的中心点位置,我们可以用这个来推出起始位置。
一般情况下,回文串的中点位置减去他的半径就是他的起始点位置,我们的半径的定义是他左边的字符的长度 + 1。这样一减去,就会到他起始点的前一个字符。
我们其实只需要加一就好了,但是 O I e r \tt OIer OIer 们都喜欢在这个字符串前面在加一个不同的字符防止访问越界,顺便就弥补了这个问题。
s t r : @ # a # b # a # c # a # b # a # b # b # \tt str:@\#a\#b\#a\#c\#a\#b\#a\#b\#b\# str:@#a#b#a#c#a#b#a#b#b#
我们需要注意,这里只是在添加了 # \tt \# # 之后的字符串,我们如果要找回之前的,我们只需要 ÷ 2 \tt\div~2 ÷ 2 就行了。
好,现在只剩如何推出 P \tt P P 数组了。
我们设 m r \tt mr mr 为当前算出的回文串能到达的最右边位置 + 1(不在回文串里), i d \tt id id 为能到达最右边的位置的回文串的中心点。只需要理解这个核心句子,就几乎可以理解 M a n a c h e r \tt Manacher Manacher 了
P [ i ] = m r > i ? m i n ( P [ 2 × i d − i ] , m r − i ) : 1 \tt P[i] = mr > i~?~min(P[2 \times id - i], mr - i) : 1 P[i]=mr>i ? min(P[2×id−i],mr−i):1
我们解释一下该句子:
当 m r > i \tt mr > i mr>i 的时候, P [ i ] = P [ 2 × i d − i ] \tt P[i] = P[2 \times id - i] P[i]=P[2×id−i],但是这个回文串延伸的最远位置不能超过 m r \tt mr mr
当 m r < = i \tt mr <= i mr<=i 时,回文串的半径初始化为 1 \tt 1 1。、
我们只需要理解 P [ i ] = P [ 2 × i d − i ] \tt P[i] = P[2 \times id - i] P[i]=P[2×id−i],就能理解这个句子了。
我们可以把这个句子转换成另一个: P [ i ] = P [ i d − ( i − i d ) ] \tt P[i] = P[id - (i - id)] P[i]=P[id−(i−id)]
i d − ( i − i d ) \tt id - (i - id) id−(i−id) 的意思是在 i d \tt id id 往前移动 i \tt i i 到 i d \tt id id 的距离。还记得吗, i d \tt id id 代表延伸最远的回文串的中心点。
因为回文串是两边对称的,这个回文串中间如果包含回文串,而且包含的回文串不是在中点的话,那么肯定有两个。比如说:
s t r : @ # d # a # b # a # a # c # a # a # b # a # d # b # c # \tt str:@\color{red}\#d\color{blue}{\#a\#b\#a\#}\color{red}a\#c\#a\color{blue}\#a\#b\#a\#\color{red}d\#\color{black}b\#c\# str:@#d#a#b#a#a#c#a#a#b#a#d#b#c#
在红色的回文串中间,有两个对称的蓝色的回文串。
s
t
r
:
@
#
d
#
a
#
b
#
a
#
a
#
c
#
a
#
a
#
b
#
a
#
d
#
b
#
c
#
\tt str:@\color{red}\#d\color{blue}{\#a\#b\#a\#}\color{red}a\#c\#a\color{blue}\#a\#b\#a\#\color{red}d\#\color{black}b\#c\#
str:@#d#a#b#a#a#c#a#a#b#a#d#b#c#
i
d
i
\tt \kern 0.525em\kern 0.525em\kern 0.525em\kern 0.525em\kern 0.3em\kern 0.525em\kern 0.525em\kern 0.525em\kern 0.525em\kern 0.525em\kern 0.525em\kern 0.525em\kern 0.525em\kern 0.525em\kern 0.525em\kern 0.525em\kern 0.525em\tiny{id}\normalsize\kern 0.375em\kern 0.525em\kern 0.525em\kern 0.525em\kern 0.525emi
idi
这时候, P [ 2 × i d − i ] \tt P[2\times id-i] P[2×id−i] 也就是另一个蓝色区域的半径。
记住,我们在执行完这个操作后。还需要用第二种方法(暴力优化1次后那种)来扩展这个回文串,不然算出来的回文串可能不是最长的。
代码
#include<iostream>
using namespace std;
int p[2000010];
int ans = 0;
int motherless_pull_car(string str)
{
string s = "@#";
for(int i = 0; i < str.size(); i++)
{
s += str[i];
s += '#';
}
int mr = 0, id = 0;
for(int i = 1; i <= s.size(); i++)
{
p[i] = mr > i ? min(p[(id << 1) - i], mr - i) : 1;
while(s[i - p[i]] == s[i + p[i]])
{
p[i]++;
}
if(i + p[i] > mr)
{
mr = i + p[i];
id = i;
}
ans = max(ans, p[i] - 1);
}
return 0;
}
int main()
{
string str;
cin >> str;
motherless_pull_car(str);
cout << ans << endl;
return 0;
}
例题
P3805【模板】manacher 算法 \color{3498DB}{\texttt{P3805【模板】manacher~算法}} P3805【模板】manacher 算法
P4555 [国家集训队]最长双回文串 \color{3498DB}{\texttt{P4555 [国家集训队]最长双回文串}} P4555 [国家集训队]最长双回文串
P5446 [THUPC2018]绿绿和串串 \color{9D3DCF}{\texttt{P5446 [THUPC2018]绿绿和串串}} P5446 [THUPC2018]绿绿和串串