bzoj4650: [Noi2016]优秀的拆分(二分+hash)

传送门
题意:
给一个字符串 s s s
定义一个字符串 t = A A B B t=AABB t=AABB是一种优秀的拆分,求 s s s的所有字串的优秀拆分数之和。
思路:
考虑一下 O ( n 2 ) O(n^2) O(n2)暴力,枚举 A A ∣ B B AA|BB AABB中间的竖杠,然后枚举 A ∣ A A|A AA之间的竖杠即可。
然后思考如何优化:

考虑当你确定了B的开始位置,你所关注的仅仅是这个开始位置开始的AA形式的拆分有多少个。
那么我们可以预处理出对于所有i,从第i个位置开始的AA形式的拆分数量f[i],以及在第i-1个位置结束的AA形式的拆分的数量g[i-1]。这样我们就可以直接计算出答案了。

如何预处理???
我们枚举串 A A AA AA A A A的长度 l e n len len
然后我们找出 n / l e n n/len n/len个关键点: l e n , 2 ∗ l e n , 3 ∗ l e n . . . len,2*len,3*len... len,2len,3len...
这样此时满足条件的 A A AA AA一定会跨过恰好两个相邻的关键点。
于是我们再枚举相邻关键点更新答案。
假设是 a , b a,b a,b
我们发现 A A AA AA如果成立,那么等价于是 A A A被拆分成了 S 1 , a − 1 S_{1,a-1} S1,a1的一段后缀 C C C S b , n S_{b,n} Sb,n的一段前缀 D D D
A A = C D C D AA=CDCD AA=CDCD
观察发现 C , D C,D C,D的取值范围只跟 l c s ( S 1 , a − 1 , S 1 , b − 1 ) lcs(S_{1,a-1},S_{1,b-1}) lcs(S1,a1,S1,b1) l c p ( S a , n , S b , n ) lcp(S_{a,n},S_{b,n}) lcp(Sa,n,Sb,n)有关,我们用二分 + h a s h / S A +hash/SA +hash/SA来求即可。
代码:

#include<bits/stdc++.h>
#define ri register int
using namespace std;
const int rlen=1<<18|1;
inline char gc(){
    static char buf[rlen],*ib,*ob;
    (ib==ob)&&(ob=(ib=buf)+fread(buf,1,rlen,stdin));
    return ib==ob?-1:*ib++;
}
inline int read(){
    int ans=0;
    char ch=gc();
    while(!isdigit(ch))ch=gc();
    while(isdigit(ch))ans=((ans<<2)+ans<<1)+(ch^48),ch=gc();
    return ans;
}
inline int Read(char*s){
    int top=0;
    char ch=gc();
    while(!isalpha(ch))ch=gc();
    while(isalpha(ch))s[++top]=ch,ch=gc();
    return top;
}
const int N=3e4+5;
char s[N];
int n,f[N],g[N];
const int mod=1e9+9;
typedef long long ll;
typedef unsigned long long Ull;
const Ull bas=311;
inline int add(const int&a,const int&b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(const int&a,const int&b){return a>=b?a-b:a-b+mod;}
inline int mul(const int&a,const int&b){return (ll)a*b%mod;}
struct Hash_string{
    Ull pw1[N],s1[N];
    int pw2[N],s2[N];
    inline void init(){
        pw1[0]=pw2[0]=1;
        for(ri i=1;i<=n;++i){
            pw1[i]=pw1[i-1]*bas;
            s1[i]=s1[i-1]*bas+(Ull)s[i];
            pw2[i]=mul(pw2[i-1],bas);
            s2[i]=add(mul(s2[i-1],bas),(int)s[i]);
        }
    }
    inline Ull get1(int l,int r){return s1[r]-s1[l-1]*pw1[r-l+1];}
    inline Ull get2(int l,int r){return dec(s2[r],mul(s2[l-1],pw2[r-l+1]));}
}S;
inline bool check(int l1,int r1,int l2,int r2){return S.get1(l1,r1)==S.get1(l2,r2)&&S.get2(l1,r1)==S.get2(l2,r2);}
typedef long long ll;
inline void update(int a,int b){
    int l,r,mid,L,R;
    if(s[a]!=s[b])R=0;
    l=1,R=0,r=min(b-a,n-b+1);
    while(l<=r)mid=l+r>>1,check(a,a+mid-1,b,b+mid-1)?(R=mid,l=mid+1):r=mid-1;
    l=1,L=0,r=min(a,b-a-1);
    while(l<=r)mid=l+r>>1,check(a-mid,a-1,b-mid,b-1)?(L=mid,l=mid+1):r=mid-1;
    if(L+R<b-a)return;
    ++g[a-L],--g[2*a-b+R+1];
    ++f[2*b-a-L-1],--f[b+R];
}
int main(){
    for(ri tt=read();tt;--tt){
        n=Read(s);
        S.init();
        for(ri i=1;i<=n;++i)f[i]=g[i]=0;
        for(ri len=1;len<=n;++len)for(ri a=len,b=len<<1;b<=n;a=b,b+=len)update(a,b);
        ll ans=0;
        for(ri i=1;i<=n;++i)f[i]+=f[i-1],g[i]+=g[i-1],ans+=(ll)f[i-1]*g[i];
        cout<<ans<<'\n';
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值