Codeforces 1466 G. Song of the Sirens —— kmp,想法,有丶东西

This way

题意:

给你串s0和t,t的长度为n。si的构造方法如下: s i = s i − 1 t [ i ] s i − 1 s_i=s_{i-1}t[i]s_{i-1} si=si1t[i]si1
有q个询问,问你 s k s_k sk中有多少个w

题解:

首先这个构造方法就能看出来不能直接暴力地去做。那么我们找到第一个 s i s_i si使得 ∣ s i ∣ > ∣ w ∣ |s_i|>|w| si>w。可以发现,si在之后出现的次数每次都是*2.那么我们只需要找到si中有多少个w即可。那么对于i+1~n的情况该怎么做,
我们可以发现, s j = s j − 1 C 1 s j − 1 , s k = s k − 1 C 2 s k − 1 , s_j=s_{j-1}C_1s_{j-1},s_k=s_{k-1}C_2s_{k-1}, sj=sj1C1sj1,sk=sk1C2sk1 j , k > i , C 1 = = C 2 j,k>i,C_1==C_2 j,k>iC1==C2的时候,串w的出现次数增加数量是相同的,
num[i][j]表示字符i到了第j个串的时候出现的次数,这样预处理出来所有的情况数。
然后我们对于w找到每个位置是否能作为t串增加的那一个字符,也就是处理出每个位置是否能作为si的前后缀。用两次kmp即可
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod=1e9+7;
const int N=2e6+5;
vector<string>tmp;
string s,t,ss;
int n,q;
ll num[26][N/20+5],p[N/20+5];
void init(){
    int len=s.length(),now=0;
    ss=s;
    while(len<=N&&now<=n){
        tmp.push_back(ss);
        ss=ss+t[now++]+ss;
        len=len*2+1;
    }

    for(int i=0;i<26;i++){
        for(int j=1;j<=n;j++)
            num[i][j]=(num[i][j-1]*2+(t[j-1]-'a'==i))%mod;
    }
    p[0]=1;
    for(int i=1;i<=n;i++)p[i]=p[i-1]*2%mod;
}
int f[N*2];
void kmp(string &st){
    f[0]=-1,f[1]=0;
    int len=st.length();
    int i=1,j=0;
    while(i<len&&j<len){
        if(j==-1||st[i]==st[j])f[++i]=++j;
        else j=f[j];
    }
}
string w,pre,suf;
bool can[2][N/2+5];
void cal(int id,int now){
    int len=w.length();
    memset(can[id],0,sizeof(bool)*(len+1));
    while(now>=0)can[id][now]=1,now=f[now];
    if(now>=0)can[id][now]=1;
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>n>>q>>s>>t;
    init();
    while(q--){
        int k;
        cin>>k>>w;
        int now=0,wlen=w.length();
        while(now<k&&tmp[now].size()<=wlen)now++;
        if(tmp[now].size()<wlen){
            printf("0\n");
            continue;
        }
        ll ans=0;
        pre=w+"#"+tmp[now];
        kmp(pre);
        int len=pre.length();
        cal(0,f[len]);

        for(int i=wlen;i<=len;i++)
            ans+=f[i]==wlen;
        ans=ans*p[k-now]%mod;

        suf=tmp[now]+"#"+w;
        kmp(suf);
        cal(1,f[len]);
        for(int i=0;i<wlen;i++){
            if(!can[0][i]||!can[1][wlen-i-1])continue;
            int v=w[i]-'a';
            ans=(ans+num[v][k]-num[v][now]*p[k-now])%mod;
            if(ans<0)ans+=mod;
        }
        printf("%lld\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值