manacher 算法
定义
最大回文半径
一个长度为奇数的回文串以中心为起点,到它某一端点的连线,所经过的字符个数,包括两端。
例如:
s
=
a
b
c
d
a
s = abcda
s=abcda
c c c是回文串的中心,该字符串的最大回文半径是 3 3 3。
m r , L : mr ,L: mr,L:
mr 表示已经遍历的回文串的最靠右的端点,L表示最靠左的端点。
初始化为0。
m i d : mid : mid:
表示当前 m r mr mr所对应的文串的中心的位置的下标。
i
:
i :
i:
表示当前遍历到的字符的位置。
i
’
:
i’ :
i’:
表示当前遍历到的字符的位置关于
m
i
d
mid
mid对称的点,
i
′
=
m
i
d
∗
2
−
i
i' = mid * 2 - i
i′=mid∗2−i,由于是刚刚遍历到
i
i
i那么
i
i
i一定在
m
i
d
mid
mid的右边,
i
′
i'
i′一定在
m
i
d
mid
mid的左边。
Manacher算法的作用
对于每一个字符串里面的字符,我们都能以线性的时间复杂度求出其最长回文半径。
Mancher算法的具体操作:
1.初始化:
首先,为了方便处理长度为偶数的字符串,我们把所有的偶字符串全部转化为长度为奇数的字符串。转化方式,对于原串的开头我们加上$
结尾加上^
,每个字符的两端我们加
上#
例如,对于字符串:
abcba
abcba
转化后的字符串
$#a#b#c#b#a#^
初始化代码:
void init(char a[]){//把原始的串a转化为都是奇数长度的串b
int k = 0;
b[k ++] = '$',b[k ++ ] = '#';
for(int i = 0; i < n; i ++ ){
b[k ++ ] = a[i];
b[k ++ ] = '#';
}
b[k ++ ] = '^';
n = k;
}
然后从左往右遍历字符串,设当前位置是 i i i,讨论并更新 m r , m i d , i mr, mid, i mr,mid,i
-
i
<
m
i
d
<
m
r
,
L
<
i
’
−
p
[
i
′
]
<
m
i
d
i < mid < mr, L < i’ - p[i'] < mid
i<mid<mr,L<i’−p[i′]<mid
这时,根据对称性可知 p [ i ] = p [ i ′ ] p[i] = p[i'] p[i]=p[i′]
-
i
<
m
i
d
<
m
r
,
i
′
−
p
[
i
′
]
<
L
i < mid < mr, i' - p[i'] < L
i<mid<mr,i′−p[i′]<L
这时
p [ i ] = m r − i p[i] = mr - i p[i]=mr−i
证明:假如 p [ i ] p[i] p[i]还能向右扩展,那么:
由对称性得:
x x x和 y y y关于 i i i对称, 所以 x = y x = y x=y
y y y和 a a a关于 m i d mid mid对称,所以 y = a y = a y=a,
a a a和 b b b关于 i ′ i' i′对称,所以 a = b a = b a=b,
所以 b = x b = x b=x。所以之前的 L , m r L,mr L,mr都可以向外继续扩展
所以矛盾。
3. i < m i d < m r , i ′ − p [ i ′ ] = = L i < mid < mr, i' - p [i'] == L i<mid<mr,i′−p[i′]==L
这时,最小的 p [ i ] m i n = m r − i p[i]_{min} = mr - i p[i]min=mr−i
但是不知道后面的情况,所以
p
[
i
]
⩾
p
[
i
]
m
i
n
p[i] \geqslant p[i]_{min}
p[i]⩾p[i]min
我们需要暴力向
m
r
mr
mr右边扩,知道找到
p
[
i
]
p[i]
p[i]为止
4.
i
>
m
r
i > mr
i>mr
这时先设 p [ i ] = 1 p[i] = 1 p[i]=1,再暴力向外扩张。
代码
void manacher(char b[]){
int mr = 0, mid;//mr不取到右端点,取的是开区间
for(int i = 1; i < n; i ++ ){
if(i < mr) p[i] = min(p[2 * mid - i], mr - i);
else p[i] = 1;
while(b[i - p[i]] == b[i + p[i]]) p[i] ++;
if(i + p[i] > mr){
mr = i + p[i];
mid = i;
}
}
}
时间复杂度
O
(
N
)
O(N)
O(N)
因为
p
[
i
]
p[i]
p[i]如果变大,超过
m
r
mr
mr,
m
r
mr
mr也是一直向右更新,所以里面的
w
h
i
l
e
while
while执行的次数为
n
n
n次,所以时间复杂度是
O
(
N
)
O(N)
O(N)的。