Hdu 5785 Interesting(给你一个字符串,你可以选择这个字符串的一个子串,记为s[l...r] 你可以选择一个k(i<=k< j)将这个子串划分成两半,如果这两半都是回文串,那么答案)

传送门:Hdu 5785 Interesting


给你一个字符串,你可以选择这个字符串的一个子串,记为s[l…r]
你可以选择一个k(i<=k< j)将这个子串划分成两半,如果这两半都是回文串,那么答案加上l*r,
同一个子串可以被划分多次,问最后的答案为多少。

思路:首先我们先对原来的子串进行一次manacher,那么假设以i这个点为分割点
分成s[l…i]和s[i+1…r],那么这个对答案的贡献便为(x1+x2+…+xn)*(y1+y2+…+ym)
x1,x2…xn表示和i点为终点构成回文串的起点,y1,y2,…ym表示和j点为起点构成回文串的终点

接下来的工作便是统计x1+…+xn和y1+…+yn
manacher中的p[i]表示以i为中心的(包含i这个字符的回文串半径长)

num1的前缀和表示以这个点为结尾的回文串的总个数,cnt1[i]表示到每个位置为中心的位置的总和(用double表示)
那么x[i]等于2*cnt1[i]-i*num1[i]

对于i为偶数的那些位置,num1[i/2]++,num1[i/2+p[i]/4+1]–;(对应回原来的字符串中),
同时记录一下cnt1[i/2]+=i/2,cnt1[i/2+p[i]/4+1]-=i/2;(表示到每个位置为中心的位置的总和)

对于i为奇数的那些位置,num1[i/2+1]++,num1[i/2+p[i]/4+1]–;
同时记录一下,cnt1[i/2+1]+=1.0*i/2,cnt1[i/2+p[i]/4+1]-=1.0*i/2

num2前缀和表示以这个点为开头的回文串的总个数,cnt2[i]表示到每个位置为中心的位置的总和(用double表示)
那么y[i]等于2*cnt2[i]-i*num2[i]

对于i为偶数的那些位置,num2[i/2+1]–,num2[i/2-p[i]/4]++;(对应回原来的字符串中),
同时记录一下cnt2[i/2-p[i]/4]+=1.0*i/2,cnt2[i/2+1]-=1.0*i/2

对于i为奇数的那些位置,num2[i/2+1]–,num2[(i+1)/2-p[i]/4]++;
同时记录一下cnt2[(i+1)/2-p[i]/4]+=1.0*i/2,cnt2[i/2+1]-=1.0*i/2

又因为cnt最终都是要乘以2的,所以刚开始的除以2就不要了


#include<bits/stdc++.h>
using namespace std;
const int maxn=2001000;
const int MOD=1000000007;
char s[maxn],str[maxn];
int p[maxn];
long long num1[maxn],num2[maxn];
long long cnt1[maxn],cnt2[maxn];

int main(){
    while(scanf("%s",str+1)!=EOF){
        int len=strlen(str+1);
        for(int i=1;i<=len+1;i++)
            s[i*2]=str[i],s[i*2-1]='#';
        s[0]='*';
        int id=0,maxv=0;
        memset(cnt1,0,sizeof(cnt1));
        memset(cnt2,0,sizeof(cnt2));
        memset(num1,0,sizeof(num1));
        memset(num2,0,sizeof(num2));
        memset(p,0,sizeof(p));
        for(int i=1;i<=len*2;i++){
            if(p[id]+id>i)
                p[i]=min(p[2*id-i],p[id]+id-i);
            else
                p[i]=1;
            while(s[i-p[i]]==s[i+p[i]])
                ++p[i];
            if(i+p[i]>id+p[id])
                id=i;
        }
        long long ans=0;
        for(int i=1;i<=len*2;i++){
            p[i]=p[i]*2-1;
            if(i&1){
                num1[i/2+1]++,num1[i/2+p[i]/4+1]--;
                cnt1[i/2+1]+=i,cnt1[i/2+p[i]/4+1]-=i;
                num2[i/2+1]--,num2[(i+1)/2-p[i]/4]++;
                cnt2[(i+1)/2-p[i]/4]+=i,cnt2[i/2+1]-=i;
            }
            else{
                num1[i/2]++,num1[i/2+p[i]/4+1]--;
                cnt1[i/2]+=i,cnt1[i/2+p[i]/4+1]-=i;
                num2[i/2+1]--,num2[i/2-p[i]/4]++;
                cnt2[i/2-p[i]/4]+=i,cnt2[i/2+1]-=i;
            }
        }
        for(int i=1;i<=len;i++)
            cnt1[i]+=cnt1[i-1],num1[i]+=num1[i-1],cnt2[i]+=cnt2[i-1],num2[i]+=num2[i-1];
        for(int i=1;i<len;i++)
            ans=(ans+( (cnt1[i]-num1[i]*i)%MOD*( (cnt2[i+1]-num2[i+1]*(i+1)) %MOD))%MOD)%MOD;
        printf("%lld\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值