Manacher算法是查找一个字符串的最长回文子串的线性算法
设原数组为s,先构造一个a数组:把特殊的字符插进s的每个字符之间,注意下标从1开始
例如s【1-5】:1 1 1 1 1
a【1-10】:-1 1 -1 1 -1 1 -1 1 -1 1
然而当我们防止边界问题往往会把a【0】设置成一个不同于插入字符的字符,这个可以是-2;
那么a【0-10】:-2 -1 1 -1 1 -1 1 -1 1 -1 1
然后我们再构造一个len数组来表示以i为中心的最大回文串的最右端的字符到i之间(包括i和这个数)的最大长度是多少。先设一个mx=0来表示最右边的数的坐标,po表示以po为中心的点的坐标。计算方法:当i<mx时:len[i]=min(mx-i,a[2*po-i])否则len[i]=1;再算len应该是多少:当以i为中心以len往外的字符串相等时我们就len[i]++;当更新完毕之后如果i+len[i]>mx,我们就要更新一下mx和po:mx=[+len[i],po=i;
代码如下:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int N=3e6+10;
int a[N],len[N];
int n,x;
int main(){
cin>>n;
a[0]=0;
for(int i=1;i<=n;i++){//构造数组
cin>>x;
a[i*2-1]=-1;
a[i*2]=x;
}
a[2*n+1]=0;
int mx=0,po=0;
for(int i=1;i<=2*n;i++){//算len数组
if(i<mx){
len[i]=min(mx-i,len[2*po-i]);
}else len[i]=1;
while(a[i+len[i]]==a[i-len[i]])len[i]++;
if(i+len[i]>mx){
mx=i+len[i];
po=i;
}
}
return 0;
}
如果想求每个数为头的字符串的数量,要用b数组来存,每次更新完len时b[i-len[i]+1]++,b[i+1]--
最后对b数组求前缀和当下标为偶数时输出就行。
例题:花非花(第十八届西南科技大学ACM程序设计竞赛(同步赛))
链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
给出一个序列 ana_nan,对于 iii,求有几个数 jjj 满足:
1 、i≤j
2 、子串 ai∼j 是一个回文串
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=3e6+10;
int a[N],b[N],len[N];
int n,x;
int main(){
cin>>n;
a[0]=0;
for(int i=1;i<=n;i++){//构造一个数组
cin>>x;
a[i*2-1]=-1;
a[i*2]=x;
}
a[n*2+1]=0;
int mx=0,po=0;//mx是上次更新时最长子串最右边的数的坐标,po是中间对称的数的坐标
for(int i=1;i<=2*n;i++){
if(i<mx){
len[i]=min(mx-i,len[2*po-i]);//
}else len[i]=1;
while(a[i-len[i]]==a[i+len[i]])len[i]++;
b[i-len[i]+1]++;
b[i+1]--;
if(i+len[i]>mx){
mx=i+len[i];
po=i;
}
}
for(int i=1;i<=2*n;i++){
b[i]+=b[i-1];
if(i%2==0)printf("%d ",b[i]);
}
return 0;
}