一、背景
1975年,Manacher发明了Manacher算法(马拉车算法),是一个可以在 O ( n ) O(n) O(n)的复杂度中返回字符串s中最长回文子串长度的算法。
二、算法过程分析
1.输入转化
回文串分为奇回文与偶回文,例如, ′ a b a b a ′ 'ababa' ′ababa′中字符个数为5且为回文串,所以它是奇回文,而’abba’字符个数为4且为回文串,所以它是偶回文。
显然,奇回文与偶回文很是不一样,比较难处理,所以我们将输入的字符串转换一下,规则如下:
1.在第一个字符前添加一个不常用字符(常用'$'),以此充当边界
2.在最后一个字符后添加一个不常用字符(常用'\0'),以此充当边界
3.在第一个字符前('$'后),最后一个字符后('\0'前)和每两个字符之间添加一个不常用字符(常用'#')
这样以后,上文的’ababa’就会变成’a#b#a#b#a’(边界未标出),‘abba’就会变成’a#b#b#a’,它们都是奇回文,这样就会更好处理。
2.过程分析
设 F i F_i Fi表示以 i i i为中点,回文字符串的最大半径长度。
举个例子。令字符串S=‘abbadcacda’
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
S S S | $ | # | a | # | b | # | b | # | a | # | d | # | c | # | a | # | c | # | d | # | a | # | \0 |
F i F_i Fi | ϕ \phi ϕ | 1 | 2 | 1 | 2 | 5 | 2 | 1 | 2 | 1 | 2 | 1 | 2 | 1 | 8 | 1 | 2 | 1 | 2 | 1 | 2 | 1 | ϕ \phi ϕ |
显然,最终的答案就为 M a x s i ∈ S F i − 1 Max_{s_i \in S}F_i-1 Maxsi∈SFi−1
那么如何求 F i F_i Fi呢?
来看一幅图~
其中, j j j表示 i i i关于 M i d Mid Mid的对称点, N N N表示 M M M关于 M i d Mid Mid的对称点,且 M M M= M i d Mid Mid+ F M i d F_{Mid} FMid
具体来说, M M M就是以 M i d Mid Mid为中心的最长回文右边界, i i i为当前所求编号。
如果
i
<
M
i<M
i<M(如图),显然有
F
i
F_i
Fi=min(
F
j
F_j
Fj,
j
−
N
j-N
j−N)
现在知道 N N N ~ M i d Mid Mid这段与 M i d Mid Mid ~ M M M这段关于 M i d Mid Mid对称,且 j − F j j-F_j j−Fj ~ j j j这段与 j j j~ j + F j j+F_j j+Fj这段关于 j j j对称,那么显然有:
①当 j − F j > = N 时 j-F_j>=N时 j−Fj>=N时, i − F j i-F_j i−Fj ~ i i i这段与 i i i ~ i + F j i+F_j i+Fj这段关于 i i i对称
②当 j − F j < N 时 j-F_j<N时 j−Fj<N时, M i d Mid Mid ~ i i i这段与 M i d Mid Mid~ M M M这段关于 i i i对称
综 上 , 有 F i = { m i n ( F j , j − N ) = m i n ( F M i d ∗ 2 − i , M − i ) , i < M 1 , i > M 综上,有F_i= \begin{cases} \color{black}min(F_j,j-N)=min(F_{Mid*2-i},M-i),i<M \\\color{black}1,i>M\\ \end{cases} 综上,有Fi={min(Fj,j−N)=min(FMid∗2−i,M−i),i<M1,i>M
这样以后就可以在O(1)的时间复杂度内知道 i − F i i-F_i i−Fi ~ i + F i i+F_i i+Fi必定为回文字符串,那么再从此向外暴力寻找,就可以得到 F i F_i Fi的最终值
若最终的 F i + i > M F_i+i>M Fi+i>M,那么为了让后面的 F F F值可以更快地求出,我们需要更新 M i d Mid Mid与 M M M值,代码如下:
if(F[i]+i>M)
M=F[i]+i,Mid=i;
三、蒟蒻的代码展示
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=11000010;
char s[N*2];
int F[N*2],ans,pl;
void readn()
{
char ch=getchar();
s[0]='S',s[1]='#';pl=1;//注意从1开始
while(ch<'a'||ch>'z')ch=getchar();
while(ch>='a'&&ch<='z')s[++pl]=ch,s[++pl]='#',ch=getchar();
s[++pl]='\0';
}
int main()
{
readn();
for(int i=0,M=0,Mid=0;i<=pl;i++)
{
if(M>i)F[i]=min(F[(Mid<<1)-i],M-i);
else F[i]=1;
while(s[i-F[i]]==s[i+F[i]])F[i]++;
if(F[i]+i>M)M=F[i]+i,Mid=i;
ans=max(ans,F[i]-1);
}
printf("%d\n",ans);
return 0;
}
//**月雩·薇嫭**