功能
马拉车算法(Manacher’s Algorithm):用 O ( n ) O(n) O(n)的时间得到一个字符串中,以每个字符为中心的最长回文的长度。
马拉车算法
初始化
由于奇数长度的回文串和偶数长度的回文串不太一样,所以我们用玄学操作将一个串里面所有偶数长度回文串变成奇数长度:
例如这个串:
S
0
=
a
b
b
a
b
a
S_0=abbaba
S0=abbaba,在其中插入特殊字符使其变成:
S
=
#
a
#
b
#
b
#
a
#
b
#
a
#
S=\#a\#b\#b\#a\#b\#a\#
S=#a#b#b#a#b#a#。
接下来都对
S
S
S操作,因为
S
S
S中所有的回文串的长度都是奇数(因为,如果有一个偶数长度的回文串,那么它的一端一定是
#
\#
#,而另一端是字母,与它是回文串矛盾)。
主体
设 d p [ i ] dp[i] dp[i]表示以 i i i为中心,最长回文串的半径。下表是 S S S的各个 d p dp dp值:
i i i | 1 1 1 | 2 2 2 | 3 3 3 | 4 4 4 | 5 5 5 | 6 6 6 | 7 7 7 | 8 8 8 | 9 9 9 | 10 10 10 | 11 11 11 | 12 12 12 | 13 13 13 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
S [ i ] S[i] S[i] | # \# # | a a a | # \# # | b b b | # \# # | b b b | # \# # | a a a | # \# # | b b b | # \# # | a a a | # \# # |
d p [ i ] dp[i] dp[i] | 1 1 1 | 2 2 2 | 1 1 1 | 2 2 2 | 5 5 5 | 2 2 2 | 1 1 1 | 2 2 2 | 1 1 1 | 4 4 4 | 1 1 1 | 2 2 2 | 1 1 1 |
只要把 d p dp dp算出来了,问题就解决了。
如果用暴力算法,可以在 O ( n 2 ) O(n^2) O(n2)的时间内算出 d p dp dp,但是既然是 d p dp dp,就要学会用前面的回文计算现在的回文。
定义如下几个变量(一定要理解):
i
:当前要计算的位置。(i
之前已经计算出来了)right
:当前计算了的,右端点最远的回文串,的右端点。
例如当枚举到i=6
时,right=9
,因为右端点最远的回文串是#a#b#b#a#
。pos
:当前计算了的,右端点最远的回文串,的中心。(pos<i
)
例如当枚举到i=6
时,pos=5
,因为右端点最远的回文串是#a#b#b#a#
。
当
i
<
r
i
g
h
t
i<right
i<right时:
看这个图:
(
i
′
i'
i′是
i
i
i关于
p
o
s
pos
pos的对称点)
由于红色一段是回文,所以
d
p
[
i
]
dp[i]
dp[i]可能等于
d
p
[
i
′
]
dp[i']
dp[i′]。
为什么说可能,因为还有这种情况:
此时以
i
i
i为中心的最长回文串并不等于以
i
′
i'
i′为中心的最长回文串,因为加粗的一段一定(因为红色一段是以
p
o
s
pos
pos为中心的最长回文)不属于以
i
i
i为中心的回文。而此时
d
p
[
i
]
=
r
i
g
h
t
−
i
dp[i]=right-i
dp[i]=right−i。
这两种情况取较小值:
d
p
[
i
]
=
min
{
d
p
[
2
×
p
o
s
−
i
]
,
r
i
g
h
t
−
i
}
dp[i]=\min\{dp[2\times pos-i],right-i\}
dp[i]=min{dp[2×pos−i],right−i}
这里得到的
d
p
[
i
]
dp[i]
dp[i]是一个最少值,有可能还能变大,还要继续扩张。
当 i ≥ r i g h t i\geq right i≥right时:暴力计算。
最后记得更新 p o s pos pos和 r i g h t right right。
时间复杂度
暴力更新(代码24行的while
)时,总体上每个点只会被暴力访问一遍,所以是
O
(
n
)
O(n)
O(n)的。
(虽然初始化过后长度扩大了一倍,导致有个常数)
代码
防止暴力更新时越界要在开头另外加一个特殊字符。
感谢Little-Qiao的提醒,要在最后再加一个不一样的字符(@
),否则会被上一组没清空的数据hack掉,但hihocoder的数据有点小弱……
板题:最长回文。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAXN 110000
int dp[MAXN*2+5];//注意数组大小
char str[MAXN*2+5];
int Manacher(char *s){
int N=0,len=strlen(s+1);
str[0]='$';//防止越界
for(int i=1;i<=len;i++){
str[++N]='#';
str[++N]=s[i];
}
str[++N]='#',str[++N]='@';//最后再加一个@
int right=0,pos=0;
for(int i=1;i<=N;i++){
if(i<right)
dp[i]=min(dp[2*pos-i],right-i);
else
dp[right=i]=1;
while(str[i-dp[i]]==str[i+dp[i]])
dp[i]++;
if(i+dp[i]>right)
right=i+dp[i],pos=i;
}
int ret=0;
for(int i=1;i<=N;i++)
ret=max(ret,dp[i]-1);//找最长回文
return ret;
}
char S[MAXN+5];
int main(){
while(~scanf("%s",S+1))
printf("%d\n",Manacher(S));
}