Manacher算法
马拉车算法,O(n)预处理每个点为中心时的回文串长度
//aa -> $#a#a#
//aba -> $#a#b#a#
char s[N];
char ss[2*N];
int p[2*N];
void manacher(char s[],int len){
ss[0]='$';
for(int i=1;i<=2*len+1;i+=2)ss[i]='#';
for(int i=0;i<len;i++)ss[2*i+2]=s[i];
int Len=2*len+1;
int mx=0,id=0;
int MaxLen=0;
for(int i=0;i<Len;i++){
p[i]=i>mx?1:min(mx-i,p[2*id-i]);
while(ss[i+p[i]]==ss[i-p[i]])p[i]++;
MaxLen=max(MaxLen,p[i]-1);//回文长度==p[i]-1
if(i+p[i]>mx)mx=i+p[i],id=i;
}
}
首先,在每个字符之间插入‘#’,因为aba可以以b为中心,但是aa就不行了,所以变成#a#a#,避免偶数串的讨论
模板里面转化后的串为$#a#a#,因为不如不使ss[0]变化即:"\0#a#a#",在
while(ss[i+p[i] ]==ss[i-p[i] ] )p[i]++;
的时候,ss[0]和char的结束字符都是’\0’,会出问题
而如果串从0开始"#a#a#",那么i-p[i]会变成-1越界
对于当前右端点最靠右的那个区间,设中点为id,右端点的右边那个点为mx,p[i]为新串中i为中心的回文半径
当i小于mx的时候,我们才可以优化(因为i和j关于id对称,那么至少在id这个回文串中,ij的那部分是一样的,即mx对称点~j 和 i~mx对称)
- 当j为中心的回文串在mx对称点和id上(p[j]<=mx-i)时,说明i的那个串可以直接对过去(p[i]=p[j])
- p[j]>mx-i时,因为mx之后的部分和mx对称点之前的部分不一样,所以只能得到p[i]=p[j],然后遍历mx之后的地方
之后是转化的事,因为上面求出的p[i]是在修改后串的基础上得出的
原奇数串:#b#a#b#,p[4(‘a’)]=4(a#b#),len(bab)=3=4-1
原偶数串:#a#a#,p[3(’#’)]=3(#a#),len(aa)=3-1
原来的回文长度为现在的p[i]-1
- 当i为奇数时,在新串中代表’#’,在原串中不存在,为中间两个字符的中间,代表偶数回文串
- 当i为偶数时,代表原来串的一个字符,代表奇数回文串
例题:https://cn.vjudge.net/problem/Gym-101864J
题意:
给出一个回文串,问有多少个子串中不包含长度至少k的回文串的子串
解析:
首先当然是直接套马拉车板子,得出回文串长度
如果我们可以快速处理出以i点作为右端点的串有多少个包含k长回文串,题目就做出来了
假设上述情况中有三个回文串长度>=k,以一号为例,那么以点1为结尾的串中,有x1个串包含k长回文串,并且可以知道点1后面的点也是如此。
而你发现点2以后的点有x2个,比x1多,所以更新应该是取max。
题目就变成了区间更新的问题。因为取max,所以树状数组是单调不减的,所以可以用树状数组往后更新pos,查询从pos往前。
#include<bits/stdc++.h>
using namespace std;
#define debug(i) printf("# %d\n",i)
typedef long long LL;
const int N=1e5+5;
char s[N];
char ss[2*N];
int p[2*N];
void manacher(char s[],int len){
ss[0]='$';for(int i=1;i<=2*len+1;i+=2)ss[i]='#';
for(int i=0;i<len;i++)ss[2*i+2]=s[i];
int Len=2*len+1;
int mx=0,id=0;int MaxLen=0;
for(int i=0;i<Len;i++){
p[i]=i>mx?1:min(mx-i,p[2*id-i]);
while(ss[i+p[i]]==ss[i-p[i]])p[i]++;
MaxLen=max(MaxLen,p[i]-1);//回文长度==p[i]-1
if(i+p[i]>mx)mx=i+p[i],id=i;
}
}
int tr[N];
void add(int pos,int v,int n){
while(pos<=n)tr[pos]=max(tr[pos],v),pos+=(pos&(-pos));
}
int query(int pos){
int re=0;
while(pos>=1)re=max(tr[pos],re),pos-=(pos&(-pos));return re;
}
int main(){
int t;scanf("%d",&t);
while(t--){
memset(tr,0,sizeof(tr));
int k;scanf("%d",&k);
scanf("%s",s);
int len=strlen(s);
int Len=2*len+1;
manacher(s,len);
for(int i=2;i<Len;i++){
if(p[i]-1<k)continue;
int st,en,tmp=k; //如果k=3,长度为7的回文串,我们只需要中间三个即可
if(tmp%2!=(p[i]-1)%2)tmp++; //如果k=3,长度为6的回文串,我们需要中间4个
if(i%2==0){ //#a#
en=i/2+tmp/2,st=i/2-tmp/2; //求出取出回文串的起点终点(在原串中,以1开始)
}
else{ //#a#a#
en=(i-1)/2+tmp/2,st=(i+1)/2-tmp/2;
}
add(en,st,len); //从终点开始更新,值=起点
}
LL ans=0;
for(int i=1;i<=len;i++)ans+=(LL)(i-query(i));
printf("%lld\n",ans);
}
}