算法参考:http://www.cnblogs.com/biyeymyhjob/archive/2012/10/04/2711527.html
此算法用于求回文串,可以达到o(n)的复杂度,较好的解决问题,抛去了奇数或偶数匹配的差别,下面进行应用,通过题目来讲解
题目:http://acm.split.hdu.edu.cn/showproblem.php?pid=3613
题意:给定一个字符串和一个26个价值的数组(代表26字母对应的价值),要求分成两段,其中如果一段满足回文,价值为价值和,否则为0,统计最大价值
题解:利用manacher算法进行回文串预处理,再从前往后扫描和从后往前扫描回文串,统计最大的和
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<map>
#define N 500050
using namespace std;
int zi[28]; //记录价值
char a[N]; //记录原始字符串
char b[N*2]; //记录manacher算法预处理后的字符串
int p[N*2]; //记录p值
int first[N*2]; //记录从前扫描的,以第一个字符开始的回文串
int last[N*2]; //记录从后扫描,以最后一个字符结束的回文串
int f,l; //记录以上两数组的数量
int sum[N*2]; //统计从1-i的和,要排除‘#’
void Manacher(char *c,int len)
{
f=0;l=0;
int id=0,mx=0;
for(int i=1;i<len;i++)
{
sum[i]=sum[i-1];
if(c[i]!='#'){sum[i]+=zi[c[i]-'a'];}
if(mx>i)
{
p[i]=p[id*2-i]>=mx-i?mx-i:p[id*2-i];
}
else
{
p[i]=1;
}
while(c[i-p[i]]==c[i+p[i]]&&i+p[i]<len)
{
p[i]++;
}
if(mx<i+p[i])
{
mx=i+p[i];
id=i;
}
if(i==p[i]&&p[i]!=1&&i+p[i]!=len)//以第一个字符(非#)为开始的回文串
{
first[f++]=i;
}
if(i+p[i]==len&&p[i]!=1&&i!=p[i])//以最后一个字符(非#)为结束的回文串
{
last[l++]=i;
}
}
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
for(int i=0;i<26;i++) scanf("%d",&zi[i]);
scanf("%s",a);
int len=strlen(a);
int blen=2;
b[0]='$';b[1]='#';
for(int i=0;i<len;i++)
{
b[blen++]=a[i];
b[blen++]='#';
}
Manacher(b,blen);
int ans=0;
int s=0;
for(int i=0;i<f;i++) //从前统计价值
{
int d=first[i]+p[first[i]]-1;
int d2=(blen-1+d)/2;
s=sum[d];
if(p[d2]+d2==blen){s=sum[blen-1];} //判断后面是否也构成回文串,为保证后面为奇数要思想上去掉最后的‘#’
if(ans<s) ans=s;
}
for(int i=0;i<l;i++)//从后统计价值
{
int d=last[i]-p[last[i]]+1;
int d2=(1+d)/2;
s=sum[blen-1]-sum[d-1];
if(d2-p[d2]==1){s=sum[blen-1];}
if(ans<s) ans=s;
}
printf("%d\n",ans);
}
return 0;
}
题目:http://acm.split.hdu.edu.cn/showproblem.php?pid=5340
题意:给定一个字符串,问能否分成三个回文串
题解:利用manacher进行预处理,要先扫描从第一个字符开始的回文串和以最后一个字符为结尾的回文串,然后一个个枚举,是否满足中间的剩余字符串也为回文串
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#define N 20050
using namespace std;
char a[N];
char b[N*2];
int b_p[N*2];
int first[N*2];
int last[N*2];
void Manacher(char *c,int len)
{
int id=0,mx=0;
for(int i=1;i<len;i++)
{
if(mx>i)
{
b_p[i]=b_p[2*id-i]>=mx-i?mx-i:b_p[2*id-i];
}
else
{
b_p[i]=1;
}
while(i+b_p[i]<len&&c[i-b_p[i]]==c[i+b_p[i]])
{
b_p[i]++;
}
if(mx<b_p[i]+i)
{
mx=b_p[i]+i;
id=i;
}
}
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int blen=2;
scanf("%s",a);
int len=strlen(a);
if(len<3)
{
printf("No\n");
}
else if(len==3)
{
printf("Yes\n");
}
else
{
b[0]='$';b[1]='#';
for(int i=0;i<len;i++) //预处理
{
b[blen++]=a[i];
b[blen++]='#';
}
Manacher(b,blen);
int flag=0;
int l=0,r=0;
for(int i=1;i<blen;i++)//扫描
{
if(i-b_p[i]+1==1)
{
if(b[i]=='#'&&b_p[i]==1){}
else{first[l++]=b_p[i];}
}
if(i+b_p[i]-1==blen-1)
{
if(b[i]=='#'&&b_p[i]==1){}
else{last[r++]=b_p[i];}
}
}
for(int i=0;i<l;i++)
{
for(int j=r-1;j>=0;j--)
{
int d=2*first[i]-1+blen-2*last[j]+1; //求第一个回文串的结束位置和最后一个回文串的开始位置之和
int sub=blen-2*first[i]-2*last[j]+1; //求第一个回文串的结束位置和最后一个回文串的开始位置之差,即是中间字符串的长度
if(sub<=0) break; //中间字符串不能为空
if(b_p[d/2]*2-1>=sub)//若中间字符是以该字符为中心的总长大于中间长度的回文串则符合条件
{
flag=1;break;
}
}
if(flag) break;
}
if(flag) printf("Yes\n");
else printf("No\n");
}
}
return 0;
}
题目:http://acm.split.hdu.edu.cn/showproblem.php?pid=3068
题意:求最大回文串长度
题解:manacher模板题
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
using namespace std;
char a[110050*2];
char b[110050*2];
int p[110050*2];
int Manacher(char *c,int len)
{
int mx=0,id=0;
int ans=0;
for(int i=1;i<=len;i++)
{
if(mx>i)
{
p[i]=p[2*id-i]>=mx-i?mx-i:p[2*id-i];
}
else
{
p[i]=1;
}
while(c[i-p[i]]==c[i+p[i]])
{
p[i]++;
}
if(i+p[i]>mx)
{
mx=p[i]+i;
id=i;
}
if(ans<p[i])
{
ans=p[i];
}
}
return ans;
}
int main()
{
while(~scanf("%s",a))
{
int len=strlen(a);
int co=2;
b[0]='$';b[1]='#';
for(int i=0;i<len;i++)
{
b[co++]=a[i];
b[co++]='#';
}
int d= Manacher(b,co-1);
printf("%d\n",d-1);
}
return 0;
}