给出一个只由小写英文字符a,b,c...y,z组成的字符串S,求S中最长回文串的长度.
回文就是正反读都是一样的字符串,如aba, abba等
Input
输入有多组case,不超过120组,每组输入为一行小写英文字符a,b,c...y,z组成的字符串S
两组case之间由空行隔开(该空行不用处理)
字符串长度len <= 110000
Output
每一行一个整数x,对应一组case,表示该组case的字符串中所包含的最长回文长度.
Sample Input
aaaa abab
Sample Output
4 3
题意:求一个字符串的最长回文串。
思路:马拉车算法模板。
马拉车算法:对于任何一个回文串,可能是奇数个(aba),也可能是偶数个(abba)。对于奇数特别好处理,枚举每一个字符为中点,两边找即可。对于偶数个的话就比较麻烦了。为了简化问题,我们把字符串都转化一下,每个字符前后前面都加一个无关的字符.
eg: aba --> #a#b#a# (新串为奇数个)
abba --> #a#b#b#a#(新串为奇数个)
其实原串后面还有一个 '\0' 为了简化问题(不特判结尾)我们在最开始再加一个无关字符 $
eg: aba --> $#a#b#a#0
abba --> $#a#b#b#a#0
然后我们就来找回文串了,
我们定义Mp[ i ] 是以 i 为中心的回文串的半径 (Mp[i] = 1 代表这个回文串只有自己)。
id 是当前已知最长回文串的中点, mx 是当前已知最长回文串的右端点
对于当前位置 i 它的回文半径怎么算呢?
因为我们是顺序遍历的,所以到达 i 之前的所有 Mp[ i ]都已经得到了。所有我们可以利用 i ,j 关于ID对称的特性来减少“暴力”求解的次数(当然是直求长度了)。
当 mx > i 时
①. mx - i > p[ j ] :因为上图蓝色部分已经是回文串了,也就是 i 的最小半径应该和 j 的半径相等。(Mp[ i ] >= Mp[ j ])
所以最小半径为 p [ j ]
②. mx - i < p[ j ] :黄色部分的长度小于 p [ j ] 那黄色部分再往右就不知道了, 所以最小半径为 mx - i
当 mx <= i 时,就没能利用 对称点 j 了,所以最小半径为 1
接下来就是暴力循环扩增 Mp[ i ]了
最后只需要求Mp的最大值即可,虽然Mp记录的半径,但由于字符串扩增了,所有Mp就是答案。
代码如下:
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 110010
using namespace std;
char Ma[N<<1],s[N];
int Mp[N<<1];
void Manacher(int len)
{
int l=0;
Ma[l++]='$';
Ma[l++]='#';
for(int i=0; i<len; i++)
{
Ma[l++]=s[i];
Ma[l++]='#';
}
Ma[l]=0;
int mx=0,id=0;
for(int i=0; i<l; i++)
{
Mp[i]=mx>i?min(Mp[2*id-i],mx-i):1;
while(Ma[i+Mp[i]]==Ma[i-Mp[i]])Mp[i]++;
if(i+Mp[i]>mx)
{
mx=i+Mp[i];
id=i;
}
}
}
int main()
{
while(~scanf("%s",s))
{
int len=strlen(s);
Manacher(len);
int ans=0;
for(int i=0; i<2*len+2; i++)
ans=max(ans,Mp[i]-1);
printf("%d\n",ans);
}
return 0;
}