Manacher感性瞎扯
xtx1092515503 · · 个人记录
Manacher=马拉车
大家好,我们今天来扯Manacher算法了。
I.马拉车可以干什么?
一句话:对于一个字符串
II.预处理
对于一个字符串,它的回文串可以有两种类型:
A.奇回文串
例:AACCBCCAA
特征:有单一回文中心(例中的B)。
B.偶回文串
例:AABBCCBBAA
特征:对称中心是两个(例中的BB)。
两者性质并不相同,必须分类讨论。
但是,我们有一种方法,可以将两者统一成一种类型:
在原串每两个字符之间,以及串头串尾,都加上一个无关字符(例如 # 等)。
例:
AABBCCDD
ABCBA
这样的话,原串中的奇回文串与偶回文串,都被统一成了奇回文串。(奇回文串变成以原串字符为对称中心的回文串,偶回文串变成以 # 为对称中心的回文串)。
代码(在读入时直接进行操作):
inline void getln(){
s[0]='$',S=1;
char c=getchar();
while(c>'z'||c<'a')c=getchar();
while(c>='a'&&c<='z')s[S++]=c,s[S++]='$',c=getchar();
s[S]='\0';
}
III.主算法
(默认字符串从
(暂时忽略添加进来的 # 字符,因为忽略也无影响)
先考虑暴力思路:枚举对称中心,然后枚举对称半径。总复杂度
(当然,可以哈希+二分达到
同kmp算法一样,我们可以思考一些操作来避免重复枚举。
这时候我们就可以设两个变量:
暴力延伸,这里是未涉及到的新地方。
2. i < Rpos
这时,我们令
同时令
再令
2.1. Lpos<lp
因为是回文串,所以有
则关于
如图:
2.2. Lpos\geq lp
对称的只有从
如图:
IV.实现
inline void Manacher(){
Centre=Rpos=-1;
for(register int i=0;i<S;i++){
rad[i]=(i<Rpos)?min(Rpos-i,rad[Centre*2-i]):1;
while(i+rad[i]<S&&i-rad[i]>=0)if(s[i+rad[i]]==s[i-rad[i]])rad[i]++;else break;
if(i+rad[i]>Rpos)Rpos=i+rad[i],Centre=i;
Ans=max(Ans,rad[i]);
}
}
实现与上面的描述很不一致,我们逐行分析。
rad[i]=(i<Rpos)?min(Rpos-i,rad[Centre*2-i]):1;
用了三目运算符。
它的意思是:
如果
并且我们令
然后就可以匹配了。
注意!!这个时候初始回文串长度应为
即:
rad[i]=(i<Rpos)?min(Rpos-i,rad[Centre*2-i]):0;
最后是
另,统计答案时应是:
Ans+=(rad[i]>>1);
因为加入了辅助字符 # ,所以数量要减半。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int S,rad[1001000],Rpos,Centre,Ans;
char s[1001000],to[129];
void getln(){
char c=getchar();
to['0']='1',to['1']='0',to['$']='$';
s[0]='$',S=1;
while(c!='0'&&c!='1')c=getchar();
while(c=='0'||c=='1')s[S++]=c,s[S++]='$',c=getchar();
s[S]='\0';
}
void Manacher(){
Rpos=Centre=-1;
for(int i=0;i<S;i++){
rad[i]=(i<Rpos)?min(Rpos-i,rad[Centre*2-i]):0;
while(i-rad[i]>=0&&i+rad[i]<S)if(to[s[i-rad[i]]]==s[i+rad[i]])rad[i]++;else break;
if(i+rad[i]>Rpos)Rpos=i+rad[i],Centre=i;
Ans+=(rad[i]>>1);
}
}
signed main(){
scanf("%lld",&S);
getln();
Manacher();
printf("%lld",Ans);
return 0;
}
VII.总结
马拉车还是很妙的,总思想同也是求字符串匹配的Z算法类似。有兴趣的同学可以研究一下。
完结撒Manacher~~
(实在找不到Manacher本人图片了)