CodeForces - 631D KMP

题目链接:http://codeforces.com/contest/631/problem/D

 

题意:

       给你两个个块集组成的字符串,字符串中每一块有这个字母的数量和这个字母,第一个是父亲字符串,第二个是儿子字符串,问你儿子字符串和父亲匹配次数的有多少。

 

做法:

       和普通的KMP又有点不一样,这里加上了一个数字,在正式匹配之前,当然要先去重,保证每一块和前后都不一样。然后做kmp的时候,不管是求next数组,还是正常的匹配,都要带上一个数量的数组,在数组两边也相同的时候再作处理。这里还有一个小技巧,如果子字符串的块为1或者2就直接做处理,不然先精确的做中间的kmp,把有效的头保存下来,(因为第一块和最后一块的要求只是字母相同的情况下数量不大于父亲的就可以了,但是中间是必须一模一样,所以精确做的时候取中间的就好了)。然后再看看前后是否搭配上即可。

 

#include<bits/stdc++.h>
using namespace std;
const int maxn=200505;
const int mod=(int)1e9+7;
typedef long long ll;
ll numfa[maxn],numson[maxn],numtmp[maxn];
char aimfa[maxn],aimson[maxn],aimtmp[maxn];
int nex[maxn],n,m;
vector<int> preans;
//KMP匹配得到的next由子串
void kmp_next(char *str2,ll *num,int *next,int len){
     int i,j;
    next[1] = 0;
    j = 0;
    for(int i=2;i<=len;i++){
        while(j>0&&(str2[j+1]!=str2[i]||num[j+1]!=num[i]))
            j=next[j];
        if(str2[j+1]==str2[i]&&num[j+1]==num[i])
            j++;
        next[i]=j;
    }
}

int kmp_re(char *str1,ll *num1,char *str2,ll *num2,int *next,int len1,int len2){
    int j=0;
    for(int i=1;i<=len1;i++){
        while(j>0&&(str2[j+1]!=str1[i]||num2[j+1]!=num1[i]))
            j=next[j];
        if(str2[j+1]==str1[i]&&num2[j+1]==num1[i])
            j++;
        if(j==len2){
            preans.push_back(i-len2);
            j=next[j];
        }
    }
}

char s[10];
int main(){
    ll ans=0,c;
    scanf("%d%d",&n,&m);
    int gen=0,gem=0;
    for(int i=0;i<n;i++){
        scanf("%lld-%s",&c,s+1);
        if(s[1]==aimfa[gen]){
            numfa[gen]+=c;
        }
        else{
            gen++;
            numfa[gen]=c;
            aimfa[gen]=s[1];
        }
    }
    for(int i=0;i<m;i++){
        scanf("%lld-%s",&c,s+1);
        if(s[1]==aimson[gem]){
            numson[gem]+=c;
        }
        else{
            gem++;
            numson[gem]=c;
            aimson[gem]=s[1];
        }
    }
    n=gen;
    m=gem;

    if(m==1){
        for(int i=1;i<=n;i++){
            if(aimfa[i]==aimson[1]){
                ans=(ans+max(0ll,numfa[i]-numson[1]+1));
            }
        }
    }
    else if(m==2){
        for(int i=1;i<n;i++){
            if(aimson[1]==aimfa[i]&&aimson[2]==aimfa[i+1]&&numson[1]<=numfa[i]&&numson[2]<=numfa[i+1]){
                ans++;
            }
        }
    }
    else{
        for(int i=2;i<m;i++){
            aimtmp[i-1]=aimson[i];
            numtmp[i-1]=numson[i];
        }
        kmp_next(aimtmp,numtmp,nex,m-2);
        kmp_re(aimfa,numfa,aimtmp,numtmp,nex,n,m-2);
        for(int i=0;i<preans.size();i++){
            int l=preans[i],r=l+m-1;
            if(aimfa[l]==aimson[1]&&aimfa[r]==aimson[m]&&numfa[l]>=numson[1]&&numfa[r]>=numson[m])
                ans++;
        }
    }
    printf("%lld\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值