一、目的
给定一个长度为 n n n 的字符串,manacher 以 O ( n ) O(n) O(n) 的时间、空间复杂度,对于此字符串的各位置下标 p 1 p1 p1 求出以其为中心的最长的回文子串的半径(于是可求出此回文子串长度),其中,若此回文子串的左右端点的下标为 [ p 1 − h a l f , p 1 + h a l f ] [p1-half , p1+half] [p1−half,p1+half] 则称 h a l f half half 为半径;最终得到整个字符串的半径数组 r a d i u s [ ] radius[] radius[],其中 r a d i u s [ p 1 ] radius[p1] radius[p1] 值为 p 1 p1 p1 处的半径。
二、预处理
若回文子串长度为偶数,则不方便表示其中点、半径等概念,因此先对原给定字符串 s 1 s1 s1 进行预处理,在每个字符之间插入一个特殊字符(应选用原串中不可能出现的某个字符),这样,任意的回文子串的长度都必将是奇数,且所得的回文子串与原字符串 s 1 s1 s1 中的回文子串也有清晰的对应关系。
例:abcde
⇒
\Rightarrow
⇒ #a#b#c#d#e#
这一技巧(大概?)不仅限于 manacher 算法需要用到,任意回文子串类问题都可考虑使用此技巧。
三、定义与流程
半径与半径数组:略。详见简介部分。
此算法类似 dp,利用前面位置已求得的半径( r a d i u s [ 0 , . . . , p 1 − 1 ] radius[0, ... , p1-1] radius[0,...,p1−1])以及几个特殊值(接下来要介绍的 R , L , C R,L,C R,L,C),从前往后求出各位置的半径。
假设我们当前已求出 [ 0 , p 1 − 1 ] [0,p1-1] [0,p1−1] 各下标处的半径值(即以各处为中心的最长回文子串的半径),并维护
- R = max i = 0 p 1 − 1 ( i + r a d i u s [ i ] ) R = \max_{i=0}^{p1-1} (i+radius[i]) R=maxi=0p1−1(i+radius[i]) :当前已找到的所有最长回文子串的右端所能到达的最右的位置;
- C C C :取得上述 R R R 的最值时 i i i 的最小值,于是 R = C + r a d i u s [ C ] , C < p 1 R=C+radius[C],C<p1 R=C+radius[C],C<p1;
- L = C − r a d i u s [ C ] L=C-radius[C] L=C−radius[C]。
现在我们想要求 p 1 p1 p1 处的半径 r a d i u s [ p 1 ] radius[p1] radius[p1]。设
- p 2 p2 p2: p 1 p1 p1 关于 C C C 的对称点,于是 p 2 = 2 ∗ C − p 1 p2=2*C-p1 p2=2∗C−p1;
- p 2 L p2L p2L:以 p 2 p2 p2 处为中点的最长回文子串的左端下标,于是 p 2 L = p 2 − r a d i u s [ p 2 ] p2L=p2-radius[p2] p2L=p2−radius[p2]。
接下来求 r a d i u s [ p 1 ] radius[p1] radius[p1] 并维护 R , C , L R,C,L R,C,L 的值,就要充分利用 C C C 这个中心。有四种可能情况:
- a. p 1 > R p1>R p1>R;
- b. p 1 ≤ R , p 2 L > L p1\leq R,p2L>L p1≤R,p2L>L;
- c. p 1 ≤ R , p 2 L < L p1\leq R,p2L<L p1≤R,p2L<L;
- d. p 1 ≤ R , p 2 L = L p1\leq R,p2L=L p1≤R,p2L=L;
上图中分别对各情况进行举例,每一行为一例。其中 c. d. 两种情况分别对 p 1 < R p1<R p1<R 与 p 1 = R p1=R p1=R 两种子情况进行举例,但实际上本质相同。上图仅供示意,其各情况(各行)之间显然是相互矛盾的(对应不同情况)。
a. p 1 > R p1>R p1>R:暴力扩展
此情况下, [ L , R ] [L,R] [L,R] 这一回文子串失去利用价值,只能暴力向左右试探性地不断拓展。
特别地,在最开始求 r a d i u s [ 0 ] radius[0] radius[0] 时( p 1 = 0 p1=0 p1=0),我们也希望进入此情况,因此 R R R 的初始值应设为 − 1 -1 −1。
b. p 1 ≤ R , p 2 L > L p1\leq R, p2L>L p1≤R,p2L>L: r a d i u s [ p 1 ] = r a d i u s [ p 2 ] radius[p1]=radius[p2] radius[p1]=radius[p2]
一方面,以 p 2 p2 p2 为中心、 r a d i u s [ p 2 ] radius[p2] radius[p2] 为半径的回文子串落在了已知回文的 [ L , R ] [L,R] [L,R] 内,则可根据 [ L , R ] [L,R] [L,R] 的对称性知以 p 1 p1 p1 为中心的最长回文子串的半径至少也有 r a d i u s [ p 2 ] radius[p2] radius[p2],即 r a d i u s [ p 1 ] ≥ r a d i u s [ p 2 ] radius[p1]\geq radius[p2] radius[p1]≥radius[p2];另一方面,假设 r a d i u s [ p 1 ] > r a d i u s [ p 2 ] radius[p1]>radius[p2] radius[p1]>radius[p2],由于 ( p 1 + r a d i u s [ p 1 ] + 1 ) (p1+radius[p1]+1) (p1+radius[p1]+1) 处仍处于 [ L , R ] [L,R] [L,R] 内,则可根据 [ L , R ] [L,R] [L,R] 的对称性知左边的以 p 2 p2 p2 为中心的最长回文子串仍可继续伸长,矛盾,因此 r a d i u s [ p 1 ] ≤ r a d i u s [ p 2 ] radius[p1]\leq radius[p2] radius[p1]≤radius[p2]。综上所述, r a d i u s [ p 1 ] = r a d i u s [ p 2 ] radius[p1]=radius[p2] radius[p1]=radius[p2]。
(实际上,由于 L < p 2 L ≤ p 2 L<p2L\leq p2 L<p2L≤p2,根据对称性得 p 1 < R p1<R p1<R 而不可能取等号。)
c. p 1 ≤ R , p 2 L < L p1\leq R,p2L<L p1≤R,p2L<L: r a d i u s [ p 1 ] = R − p 1 radius[p1]=R-p1 radius[p1]=R−p1
一方面,以 p 2 p2 p2 为中心、 r a d i u s [ p 2 ] radius[p2] radius[p2] 为半径的回文子串的左端超出了 [ L , R ] [L,R] [L,R] 的范围,则只有 [ L , 2 ∗ p 2 − L ] [L,2*p2-L] [L,2∗p2−L] 能够通过 [ L , R ] [L,R] [L,R] 对称性对称到右边,得到 [ 2 ∗ p 1 − R , R ] [2*p1-R,R] [2∗p1−R,R] 为回文字串,即 r a d i u s [ p 1 ] ≥ R − p 1 radius[p1]\geq R-p1 radius[p1]≥R−p1;另一方面,假设 r a d i u s [ p 1 ] > R − p 1 radius[p1]>R-p1 radius[p1]>R−p1,经过一系列对称可得 ( R + 1 ) (R+1) (R+1) 与 ( L − 1 ) (L-1) (L−1) 处字符相同,则以 C C C 为中心的最长回文子串仍可继续伸长,矛盾,因此 r a d i u s [ p 1 ] ≤ R − p 1 radius[p1]\leq R-p1 radius[p1]≤R−p1。综上所述, r a d i u s [ p 1 ] = R − p 1 radius[p1]=R-p1 radius[p1]=R−p1。
d. p 1 ≤ R , p 2 L = L p1\leq R,p2L=L p1≤R,p2L=L:暴力扩展
上述 b. c. 情况在证明 r a d i u s [ p 1 ] radius[p1] radius[p1] 上界时,我们利用矛盾来进行反证,其中
- b. 情况是与以 p 2 p2 p2 为中心的最长回文子串的矛盾,其中也利用了以 C C C 为中心的最长回文子串的对称性;
- c. 情况是与以 C C C 为中心的最长回文子串的矛盾,其中也利用了以 p 2 p2 p2 为中心的最长回文子串的对称性;
然而,当 p 2 L = L p2L=L p2L=L 时,上述矛盾均无法构造。因此只能暴力向左右试探性地不断拓展。注意,此时已知 r a d i u s [ p 1 ] ≥ R − p 1 radius[p1]\geq R-p1 radius[p1]≥R−p1,无需从 p 1 p1 p1 处开始向左右扩展。
上述四种情况中,b. c. 所得的新的最长回文子串右端都没超过 R R R,于是不用更新 R , C R,C R,C;只有 a. d. 情况的暴力扩展需要更新。
上述每次暴力扩展(a. d. 情况)的复杂度都是 O ( R 2 − R 1 ) O(R_2-R_1) O(R2−R1),其中 R 1 R_1 R1 为原来的 R R R 值、 R 2 R_2 R2 为更新后的 R R R 值( R 2 > R 1 R_2>R_1 R2>R1);每次 b. c. 情况计算的复杂度都是 O ( 1 ) O(1) O(1)。那么,从前往后处理得到 r a d i u s [ ] radius[] radius[] 数组的总复杂度就是 O ( n ) O(n) O(n),其中 n n n 为原字符串(或预处理后字符串,反正只差 2 2 2 倍的常数)长度。
四、参考代码
#include<cstdio>
char s1[200005]; // 输入的字符串
char s2[400009]; // 预处理后的字符串,大小开2倍!
int radius[400009]; // 开2倍!
void manacher(){
// 预处理
int i;
for(i=0 ; s1[i]>0 ; ++i){
s2[i*2] = '#'; // 注意选用原串中不可能出现的某个字符
s2[i*2+1] = s1[i];
}
s2[i*2] = '#'; // 注意选用原串中不可能出现的某个字符
s2[i*2+1] = 0;
int len=i*2+1;
// manacher
int R=-1, C=-1;
for(int p1=0 ; p1<len ; ++p1){
if(p1>R){ // 情况a
int half = 0;
while(p1+half+1<len && p1-half-1>=0 && s2[p1+half+1]==s2[p1-half-1]) ++half;
radius[p1] = half; // [p1-half , p1+half] is palindromic
R = p1+half;
C = p1;
}else{
int L = 2*C-R;
int p2 = 2*C-p1;
int p2L = p2-radius[p2];
if(p2L > L){ // 情况b
radius[p1] = radius[p2];
}else if(p2L < L){ // 情况c
radius[p1] = R-p1;
}else{ // 情况d
int half = R-p1; // 不需要从0开始找
while(p1+half+1<len && p1-half-1>=0 && s2[p1+half+1]==s2[p1-half-1]) ++half;
radius[p1] = half; // [p1-half , p1+half] is palindromic
R = p1+half;
C = p1;
}
}
}
}
int main(){
while(scanf("%s" , s1)>0){
manacher();
//for(int i=0 ; s2[i]>0 ; ++i) printf("i=%d : %c , %d\n" , i , s2[i] , radius[i]);
int max_ra=-1, max_pos=-1; // 找出最大半径及其第一次出现的位置
for(int i=0 ; s2[i]>0 ; ++i) {
if(max_ra < radius[i]){
max_ra = radius[i];
max_pos = i;
}
}
if(max_ra<=1){
printf("No solution!\n");
}else{
int L = (max_pos - radius[max_pos])/2;
int R = (max_pos + radius[max_pos])/2 - 1;
printf("第一个最长回文子串的左右端点下标:%d %d\n" , L , R);
for(int i=L ; i<=R ; ++i) printf("%c" , s1[i] );
printf("\n");
}
}
return 0;
}
(由 manacher 模板题 HDU-3294 的代码改写)
上面在输出第一个最长回文子串的左右端点的下标时,遇到了由预处理后字符串下标转换得到原串中的下标的小烦恼。只需记住:预处理后字符串中,偶数位下标的字符都是 #
,其下标与其右边的字符的下标整除2得到原串中对应字符的下标;另外,求得的最长回文子串的左右端必然是 #
。
参考资料
《Manacher算法的详细讲解》
https://www.jianshu.com/p/116aa58b7d81
感谢参考资料的作者!
上述资料与本文中的变量名的对应关系:
本文 | L | C | R | p1 | p2 | p2L |
---|---|---|---|---|---|---|
上述资料 | cL | C | R | p1 | p2 | pL |
上述资料与本文中的分类讨论的对应关系:
本文 | a | b | c | d |
---|---|---|---|---|
上述资料 | 第一种 | 第二种(1) | 第二种(2) | 第二种(3) |