BZOJ 4650: [Noi2016]优秀的拆分 哈希+分块

Description

如果一个字符串可以被拆分为 AABBAABB 的形式,其中 AA 和 BB 是任意非空字符串,则我们称该字符串的这种拆
分是优秀的。例如,对于字符串 aabaabaa,如果令 A=aabA=aab,B=aB=a,我们就找到了这个字符串拆分成 AABBA
ABB 的一种方式。一个字符串可能没有优秀的拆分,也可能存在不止一种优秀的拆分。比如我们令 A=aA=a,B=baa
B=baa,也可以用 AABBAABB 表示出上述字符串;但是,字符串 abaabaa 就没有优秀的拆分。现在给出一个长度为
nn 的字符串 SS,我们需要求出,在它所有子串的所有拆分方式中,优秀拆分的总个数。这里的子串是指字符串
中连续的一段。以下事项需要注意:出现在不同位置的相同子串,我们认为是不同的子串,它们的优秀拆分均会被
记入答案。在一个拆分中,允许出现 A=BA=B。例如 cccc 存在拆分 A=B=cA=B=c。字符串本身也是它的一个子串。

Input

每个输入文件包含多组数据。输入文件的第一行只有一个整数 TT,表示数据的组数。保证 1≤T≤101≤T≤10。接
下来 TT 行,每行包含一个仅由英文小写字母构成的字符串 SS,意义如题所述。

Output

输出 TT 行,每行包含一个整数,表示字符串 SS 所有子串的所有拆分中,总共有多少个是优秀的拆分。

Sample Input

4

aabbbb

cccccc

aabaabaabaa

bbaabaababaaba
Sample Output

3

5

4

7

我们用 S[i,j]S[i,j] 表示字符串 SS 第 ii 个字符到第 jj 个字符的子串(从 11 开始计数)。第一组数据中,

共有 33 个子串存在优秀的拆分:S[1,4]=aabbS[1,4]=aabb,优秀的拆分为 A=aA=a,B=bB=b;S[3,6]=bbbbS[3,6]

=bbbb,优秀的拆分为 A=bA=b,B=bB=b;S[1,6]=aabbbbS[1,6]=aabbbb,优秀的拆分为 A=aA=a,B=bbB=bb。而剩

下的子串不存在优秀的拆分,所以第一组数据的答案是 33。第二组数据中,有两类,总共 44 个子串存在优秀的

拆分:对于子串 S[1,4]=S[2,5]=S[3,6]=ccccS[1,4]=S[2,5]=S[3,6]=cccc,它们优秀的拆分相同,均为 A=cA=c,

B=cB=c,但由于这些子串位置不同,因此要计算 33 次;对于子串 S[1,6]=ccccccS[1,6]=cccccc,它优秀的拆分

有 22 种:A=cA=c,B=ccB=cc 和 A=ccA=cc,B=cB=c,它们是相同子串的不同拆分,也都要计入答案。所以第二组

数据的答案是 3+2=53+2=5。第三组数据中,S[1,8]S[1,8] 和 S[4,11]S[4,11] 各有 22 种优秀的拆分,其中 S[1

,8]S[1,8] 是问题描述中的例子,所以答案是 2+2=42+2=4。第四组数据中,S[1,4]S[1,4],S[6,11]S[6,11],S[7

,12]S[7,12],S[2,11]S[2,11],S[1,8]S[1,8] 各有 11 种优秀的拆分,S[3,14]S[3,14] 有 22 种优秀的拆分,

所以答案是 5+2=75+2=7。

题解

这道题的思路确实挺神的,我们先枚举AABB中A的长度i,然后在字符串上每隔i位设置一个关键点,看相邻的两个关键点最多能向左向右扩张多少,这里可以二分哈希实现,这样一个长度为i的AA串就一定经过这两个关键点,最后我们得到的是一个个区间,差分一下统计出每个点左面和右面的AA串数目即可。

#include<iostream>
#include<iomanip>
#include<cstring>
#include<string>
#include<cstdlib>
#include<cstdio>
#include<ctime>
#include<cmath>
#include<algorithm>
using namespace std;
char s[50000];
int hash1[50000];
int hash2[50000];
int base1[50000];
int base2[50000];
int mod1=23333;
int mod2=10007;
int get_hash1(int l,int r)
{
    int t=hash1[r]-hash1[l-1]*base1[r-l+1];
    t%=mod1;
    t+=mod1;
    t%=mod1;
    return t;
}
int get_hash2(int l,int r)
{
    int t=hash2[r]-hash2[l-1]*base2[r-l+1];
    t%=mod2;
    t+=mod2;
    t%=mod2;
    return t;
}
int v[50000];
int u[50000];
int main()
{
    //freopen("excellent.in","r",stdin);
    //freopen("excellent.out","w",stdout);
    int T;
    scanf("%d",&T);
    base1[0]=1;
    base2[0]=1;
    for(int i=1;i<=30000;i++) base1[i]=base1[i-1]*233%mod1;
    for(int i=1;i<=30000;i++) base2[i]=base2[i-1]*2333%mod2;
    while(T--)
    {
        memset(v,0,sizeof(v));
        memset(u,0,sizeof(u));
        scanf("%s",s+1);
        int len=strlen(s+1);
        for(int i=1;i<=len;i++)
        {
            hash1[i]=(hash1[i-1]*base1[1]+s[i])%mod1;
            hash2[i]=(hash2[i-1]*base2[1]+s[i])%mod2;
        }
        for(int L=1;L*2<=len;L++)
        {
            for(int i=L;i+L<=len;i+=L)
            {
                int j=i+L;
                int l=1,r=L;
                int jilu=0;
                while(l<=r)
                {
                    int mid=l+r>>1;
                    if(get_hash1(i-mid+1,i)==get_hash1(j-mid+1,j) && get_hash2(i-mid+1,i)==get_hash2(j-mid+1,j)) jilu=mid,l=mid+1;
                    else r=mid-1;
                }
                int LL=jilu;
                jilu=0;
                l=1,r=min(L,len-j+1);
                while(l<=r)
                {
                    int mid=l+r>>1;
                    if(get_hash1(i,i+mid-1)==get_hash1(j,j+mid-1) && get_hash2(i,i+mid-1)==get_hash2(j,j+mid-1)) jilu=mid,l=mid+1;
                    else r=mid-1;
                }
                int RR=jilu;
                l=i-LL;
                r=i+RR-L-1;
                if(l<=r)
                {
                    v[l]+=1;
                    v[r+1]-=1;
                }
                r=j+RR-1;
                l=j-LL+L;
                if(l<=r)
                {
                    u[l]+=1;
                    u[r+1]-=1;
                }
            }
        }
        long long ans=0;
        for(int i=0;i<=len;i++)
        {
            if(i!=0) u[i]+=u[i-1],v[i]+=v[i-1];
            ans+=1ll*u[i]*v[i];
        }
        cout<<ans<<endl;
    }
    //fclose(stdin);
    //fclose(stdout);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值