马拉车算法(manacher)
算法主要用于求一个字符串中最大回文子串的大小
算法主要思想:
首先初始化字符串,在字符串s的字符之间和尾部插入一个‘#’,如:“abba”变成“#a#b#b#a#,这样无论源字符串是奇数还是偶数都变成了奇数(此处可以试试哦),变成奇数我们就有了着脚点了,统一处理了奇偶的情况了。
接下来我们得有个整形数组来存储每个位置上的字符所能达到的最大回文字串的长度(此处我们记录一个一个回文串的半径),如果使用枚举的话,从每一个位置分别从两边进行暴力,这样就会很慢,而马拉车算法正是在这种情况下的升级处理,怎么处理的呢?
比如我们现在已经找到了一个目前来看最长的回文字串,那么我们看下下面这个图
我们现在处理下i这个点,因为它和j这个点是相对应的(与po对称),所以它所在的位置的最长回文字串与j应该是相等的,但是有个前提,这个前提就是j这个位置的最长回文字串的长度没有超过外面这个大的回文字串的位置,也就是左边没有超过2po-p,那如果超过了呢?就看下面的分析吧.
这时候怎么办呢,这个i位置不能确保和j一样具有j这样大的长度了(或者比j的长度还要大哦),所以我们先缓一步,先把i位置的最长回文子串的长度设为p-i,非常合理。
那么我们把这个最关键的理解了后,我们基本上就已经掌握了马拉车算法,接下来就是把i位置进行两边暴力枚举,并不断更新最长回文子串的位置与长度。
下面链接马拉车算法模板:
再看个实题,马拉车算法的板题。
最长回文(hdu 3068)
Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 26192 Accepted Submission(s): 9643
Problem Description
给出一个只由小写英文字符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
非常经典的马拉车算法板题
直接通过代码看思想。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=3e5+5;
char s[maxn],str[maxn];//s为源字符串,str为初始化的字符串
int len1,len2,p[maxn];//p数组记录每个位置的最长回文子串长度
//进行初始化
void init(int n)
{
str[0]='*';
str[1]='#';
for(int i=0;i<len1;i++){
str[i*2+2]=s[i];
str[i*2+3]='#';
}
len2=len1*2+2;
str[len2]='&';
}
//马拉车算法
void manacher()
{
int mx=0,id=0;//定义目前最长回文字串的末尾与中间位置
for(int i=1;i<len2;i++){
if(i<mx){//马拉车关键所在之处,传统方法的优化
p[i]=min(p[2*id-i],mx-i);
}else{
p[i]=1;
}
while(str[i+p[i]]==str[i-p[i]]){//再通过两边暴力枚举
p[i]++;
}
if(mx<p[i]+i){//更新最长回文子串末尾位置与中间位置
mx=p[i]+i;
id=i;
}
}
}
int main()
{
while(~scanf("%s",s)){
len1=strlen(s);
init(len1);
manacher();
int ans=0;
for(int i=1;i<len2;i++){
ans=max(ans,p[i]);
}
printf("%d\n",ans-1);
}
return 0;
}