manacher算法
上天折断了你飞翔的羽翼,你也要给自己一双翅膀!
这个算法主要处理回文串。
举个栗子:求字符串中最长回文串的长度(暴力pass掉的那种)
abacbaa
在其中插入一个符号(不能出现字符串中的,大家都懂的)#
变成了#a#b#a#c#b#a#a#(#是从1开始的,前面新构造的数组0位置要存另一种符号)
然后,构造一个p数组,p[i]表示一i为中间点的最长的回文串最右边字符到中间字符的长度,id表示当前字符
,p数组有一个性质,那就是p[i]-1就是该回文子串在原字符串S中的长度。
证明:首先在转换得到的字符串T中,所有的回文字串的长度都为奇数,那么对于以T[i]为中心的最长回文字串,其长度就为2*p[i]-1,经过观察可知,T中所有的回文子串,其中分隔符的数量一定比其他字符的数量多1,也就是有p[i]个分隔符,剩下p[i]-1个字符来自原字符串,所以该回文串在原字符串中的长度就为p[i]-1。
下面上代码(我把构造新数组的代码也打出来了呦)
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
///修改后的字符串长度是原字符串长度的两倍多一
char s[100005],T[100005];
///辅助数组Len[i]表示以字符T[i]为中心的
///长回文字串的最右字符到T[i]的长度
int p[100005];
///初始化辅助数组
void init()
{
int len=strlen(s);
T[0]='$';
T[1]='#';
for(int i=0; i<len; i++)
{
T[i*2+2]=s[i];
T[i*2+3]='#';
}
T[2*len+2]='\0';///不要忘记'\0'
}
void manacher()
{
int len=strlen(T);
int id=-1;///id为右边界最大的回文串的中心
int mx=0;///mx为以T[id]为中心的最长回文最右边界
for(int i=1; i<len; i++)
{
if(i<mx)
p[i]=min(p[id*2-i],mx-i);
else
p[i]=1;
while(T[i+p[i]]==T[i-p[i]])///判断该回文串是否还可以扩充
p[i]++;
///每走一步都要更新mx,因为我们希望mx尽可能的大,
///这样才有机会执行if(i<mx),从而降低算法的时间复杂度
if(mx<i+p[i])
{
mx=i+p[i];
id=i;
}
}
}
int main()
{
scanf("%s",s);
init();
manacher();
int len=strlen(T);
int ans=-1;
for(int i=1; i<len; i++)
ans=max(ans,p[i]-1);
cout<<ans<<endl;
return 0;
}
大家一定要按代码手动模拟一遍,特别要理解if和else这两行。mx在i的右边,最长也就是他自己呗。
最后附上一张盗来的图让大家更直观的感受到位置查找回文的过程