[NOI2016]优秀的拆分

[NOI2016]优秀的拆分

题目大意:

如果一个字符串可以被拆分为\(AABB\)的形式,其中\(A\)\(B\)是任意非空字符串,则这种拆分方式是优秀的。给出一个长度为\(n(n\le30000)\)的字符串\(S\),求其子串所有拆分方式中优秀拆分的总个数。

思路:

若用\(f[i]\)表示以\(i\)结尾的可以表示为\(AA\)形式的后缀数,用\(g[i]\)表示以\(i\)开头的可以表示为\(AA\)形式的前缀数。则答案为\(\sum_{i=1}^{n-1}f[i]\times g[i+1]\)。这样我们就把原问题转化为只需要考虑前/后缀为\(AA\)形式的问题。

枚举\(A\)的长度\(l\),每隔\(l\)设置一个关键点\(p\)。对于一对相邻的关键点\(p_i\)\(p_{i+1}\),我们求\(l_1=\min(\operatorname{lcs}(p_i,p_{i+1}),l)\)\(l_2=\min(\operatorname{lcp}(p_i,p_i+1),l)\)。若\(l_1+l_2-1\ge l\),则存在这样的\(AA\),我们可以算出所有合法位置,\(f\)\(g\)区间\(+1\),可以使用差分维护。

\(\operatorname{lcs}\)\(\operatorname{lcp}\)可以用哈希+二分实现,枚举\(l\)的时间复杂度是调和级数\(\mathcal O(n\log n)\)。总时间复杂度\(\mathcal O(n\log^2n)\)

源代码:

#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
typedef long long int64;
inline int getint() {
    register char ch;
    while(!isdigit(ch=getchar()));
    register int x=ch^'0';
    while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0');
    return x;
}
const int N=30001;
const int base=31,mod=998244353;
char s[N+1];
int n,hash[N],pwr[N],f[N],g[N];
inline int idx(const char &ch) {
    return ch-'a'+1;
}
inline int calc(const int &l,const int &r) {
    return (hash[r]-(int64)hash[l-1]*pwr[r-l+1]%mod+mod)%mod;
}
inline int lcs(const int &p1,const int &p2,const int &lim) {
    int l=1,r=std::min(p1,lim);
    while(l<=r) {
        const int mid=(l+r)>>1;
        if(calc(p1-mid+1,p1)==calc(p2-mid+1,p2)) {
            l=mid+1;
        } else {
            r=mid-1;
        }
    }
    return l-1;
}
inline int lcp(const int &p1,const int &p2,const int &lim) {
    int l=1,r=std::min(n-p2+1,lim);
    while(l<=r) {
        const int mid=(l+r)>>1;
        if(calc(p1,p1+mid-1)==calc(p2,p2+mid-1)) {
            l=mid+1;
        } else {
            r=mid-1;
        }
    }
    return l-1;
}
int main() {
    for(register int i=pwr[0]=1;i<N;i++) {
        pwr[i]=(int64)pwr[i-1]*base%mod;
    }
    for(register int T=getint();T;T--) {
        scanf("%s",&s[1]);
        n=strlen(&s[1]);
        for(register int i=hash[0]=1;i<=n;i++) {
            f[i]=g[i]=0;
            hash[i]=((int64)hash[i-1]*base+idx(s[i]))%mod;
        }
        for(register int l=1;l<=n/2;l++) {
            for(register int p1=l;p1<=n-l;p1+=l) {
                const int p2=p1+l;
                const int l1=lcs(p1,p2,l),l2=lcp(p1,p2,l);
                if(l1+l2-1>=l) {
                    f[p2-l1+l]++;
                    f[p2+l2]--;
                    g[p1-l1+1]++;
                    g[p1+l2-l+1]--;
                }
            }
        }
        for(register int i=1;i<=n;i++) {
            f[i]+=f[i-1];
            g[i]+=g[i-1];
        }
        int64 ans=0;
        for(register int i=1;i<n;i++) {
            ans+=(int64)f[i]*g[i+1];
        }
        printf("%lld\n",ans);
    }
    return 0;
}

转载于:https://www.cnblogs.com/skylee03/p/9174983.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值