马拉车算法(例题:Non Super Boring Substring )

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’,会出问题
&ThickSpace; \\\;
而如果串从0开始"#a#a#",那么i-p[i]会变成-1越界

对于当前右端点最靠右的那个区间,设中点为id,右端点的右边那个点为mx,p[i]为新串中i为中心的回文半径

当i小于mx的时候,我们才可以优化(因为i和j关于id对称,那么至少在id这个回文串中,ij的那部分是一样的,即mx对称点~ji~mx对称)

  1. 当j为中心的回文串在mx对称点和id上(p[j]<=mx-i)时,说明i的那个串可以直接对过去(p[i]=p[j])
    在这里插入图片描述
  2. 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
\to 原来的回文长度为现在的p[i]-1

  1. 当i为奇数时,在新串中代表’#’,在原串中不存在,为中间两个字符的中间,代表偶数回文串
  2. 当i为偶数时,代表原来串的一个字符,代表奇数回文串

&ThickSpace; \\\;


&ThickSpace; \\\;

例题: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);
    }
}


猜你喜欢

转载自blog.csdn.net/jk_chen_acmer/article/details/83180544