NOI2016 优秀的拆分(图解)

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

95分 O(n^2)暴力哈希处理f[i]以i为起始的AA串数量。
统计答案时O(n^2)暴力枚举AA串,与后面的f【i】匹配计入答案。

这个真的想不出来啊。。我们95分的做法慢在处理AA串的速度上。如果我们能够快速得到以i点为结尾的AA串个数和以i+1点为起始的AA点个数,那就能直接计算答案。

枚举A串的长度,每段长度设置一个计算点,我们发现一个AA串的长度为2*L,所有的长度为2*L的AA串上一定会有2个标记。我们考虑两个标记分别在2个A串上,如果这两个点在一个AA串上,两个点为对应点时分别计算这两个点的LCP和LCS。得到的这两个点的两端区间是相同的。
这里写图片描述
我们发现这种情况时恰好有一个AA串,以前面那个点(记作pre)-LCS+1为起点的串。
即:LCS+LCP>len时 才会有这样的AA串。

这里写图片描述
我们发现这两个区间有交时,两个对应点相距距离len。所以pre-LCS+1与当前点now-LCS+1是对应点。
我们完全可以找到这样两个长度为len相同的区间。

这里写图片描述
如图两端区间。然后这段区间是可以进行平移的。所以开始区间为【pre-LCS+1,pre+LCP-len】。
结束区间自己思考一下吧qwqwq。
然后进行差分最后前缀和就可以快速求出所需了。

#include<bits/stdc++.h>
using namespace std;

#define ll long long

const int MAXN=5e4+5;
const int MOD=19260817;
const int base=31;

int hash[MAXN];
ll mi[MAXN],u[MAXN],v[MAXN];
char s[MAXN];

ll gethash(int l,int r){
    ll ans=0;
    ans=hash[r]-hash[l-1]*mi[r-l+1];
    ans%=MOD;ans+=MOD;ans%=MOD;
    return ans;
}

void work(){
    int zuo,you;
    memset(hash,0,sizeof(hash));
    memset(u,0,sizeof(u));
    memset(v,0,sizeof(v));
    scanf("%s",s+1);
    int len=strlen(s+1);
    for(int i=1;i<=len;i++)hash[i]=(hash[i-1]*base+s[i])%MOD;
    for(int L=1;L*2<=len;L++){
        for(int i=L+L;i<=len;i+=L){
            int pre=i-L;
            if(s[i]!=s[pre])continue;
            int l=1,r=L,tmps=0;
            while(l<=r){//LCS 
                int mid=(l+r)>>1;
                if(gethash(pre-mid+1,pre)==gethash(i-mid+1,i))l=mid+1,tmps=mid;
                else r=mid-1;
            }
            l=1,r=L;int tmpp=0;
            while(l<=r){
                int mid=(l+r)>>1;
                if(gethash(pre,pre+mid-1)==gethash(i,i+mid-1)) l=mid+1,tmpp=mid;
                else r=mid-1;
            }
            if(tmps+tmpp>L){
                u[pre-tmps+1]++;u[pre+tmpp-L+1]--;
                v[i-tmps+L]++;v[i+tmpp]--;
            }
        }
    }
    ll ans=0;
    for(int i=1;i<=len;i++){
       u[i]+=u[i-1],v[i]+=v[i-1];   
    } 
    for(int i=1;i<len;i++) ans+=v[i]*u[i+1];
    printf("%lld\n",ans);
}


int main(){
    int t;
    mi[0]=1;for(int i=1;i<=30002;i++)mi[i]=(mi[i-1]*base)%MOD;
    scanf("%d",&t);
    while(t--)work();
    return 0;
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值