2018 BACS High School Programing Contest J.Non Super Boring Substring-manacher+双指针

在这里插入图片描述
在这里插入图片描述

题意:

给出一个字符串,求其子串中不包含长度大于等于k的回文串的子串个数

思路:

对于单串回文问题,考虑使用manacher算法进行预处理,即可处理出以每个位置为中点的最长回文半径

使用双指针技术,结合回文半径的信息,处理出以每个位置为起点,在不构成大于等于k的回文串前提下,向后能到达的最远位置

对于任何一个大于k的回文串,我们都可以将其看做长度为k或k+1的回文串,因为只要包含长度大于等于k的回文串就不合法,而任何一个长度大于k的回文串都一定包含一个长度为k或k+1的回文子串,所以只需要考虑最短的情况

由于manacher算法会对原串进行扩展,导致每个字符的下标发生变化,处理时只需要/2即可还原成原位置的下标

对于一个长度为n的字符串,其子串个数为n*(n+1)/2,对于我们处理出的每个区间,累加求和

因为两个相邻区间可能相互包含,对于被包含的部分还要容斥处理,减去多加的部分

代码:

#include<iostream>
#include<cstring>
#include <string>
#include<algorithm>
using namespace std;
const int maxl=2e5+5;
const int inf=0x3f3f3f3f;
int p[2*maxl+5];   //p[i]-1表示以i为中点的回文串长度
int map[maxl];
int k;
void Manacher(string s)
{
    string now;
    int len=s.size();
    for(int i=0;i<len;i++)      //将原串处理成%a%b%c%形式,保证长度为奇数
    {
       now+='%';
       now+=s[i];
    }
    now+='%';
    len=now.size();
    int pos=0,R=0;
    for (int i=0;i<len;i++)
    {
        if (i<R) p[i]=min(p[2*pos-i],R-i); else p[i]=1;
        while (0<=i-p[i]&&i+p[i]<len&&now[i-p[i]]==now[i+p[i]]) p[i]++;
        if (i+p[i]>R) {pos=i;R=i+p[i];}
    }

    for (int i=0;i<len;i++)             //预处理区间信息
    {
        if(p[i]-1<k) continue;         //只考虑长度大于等于k的回文串
        int flag=(p[i]-1-k)%2;        //判断该串是包含长度为k的回文子串还是长度为k+1的回文子串
        int ln;
        if(flag)
            ln=k+1;
        else
            ln=k;
        int lx=(i-ln+1)/2;             //左指针指向起点,下标还原成原串
        int rx=(i+ln-1)/2;            //右指针指最长可达位置,下标还原成原串
        map[lx]=min(map[lx],rx);     //一个起点可能被更新多次,只取最小的结果
    }
}
string a;
int main()
{
    std::ios::sync_with_stdio(false);
    int t;
    cin>>t;
    while(t--)
    {
        cin>>k;
        cin>>a;
        memset(map,inf,sizeof(map));
        Manacher(a);
        int len=a.size();
        int now=len;
        for(int i=len;i>=0;i--)          //求出以每个点为起点,在不包含大于等于k长度回文子串前提,所能到达的最远位置
        {
            if(map[i]!=inf)
                now=map[i];
            map[i]=now;
        }
        long long ans=0;
        long long pre=0;
        for(int i=0;i<len;i++)
        {
            long long tmp=map[i]-i;
            ans+=(1+tmp)*tmp/2;          //对于长度为tmp的字符串,计算其子串个数
            if(pre>=i)               //相邻两区间存在交叉,容斥处理
            {
                tmp=pre-i;
                ans-=(1+tmp)*tmp/2;          //减去多加的部分
            }
            pre=map[i];
        }
        cout<<ans<<endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ogmx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值