ZOJ 3587 扩展KMP应用

点击打开链接

题意:给定两个串,问从第一个串取出连续的两段,合并后可以拼成第二个的方法总数,这两段可以有重叠的部分

思路:用扩展KMP求出extand数组,不懂KMP的可以看这篇点击打开链接,解释的很详细,extand[i](0<=i<len)代表的是母串从第i个开始与子串的最长前缀,我们要求的是母串拼成子串的数量,例如子串的长度为len1,则它可能的情况就是第一个字符与后面所有的乘积加上前二个字符与后面所有的乘积加上前三个字符与后面所有的乘积....加到前边len1-1与后面一个的乘积。这就是结果,现在来看看第一个字符的可能性,因为我们已经求出了extand数组,并且通过它的定义我们可知只要extand[i]>=1,第一个字符的可能性就+1;第二个也一样,只要extand[i]>=2就+1;后面的情况我们可以将字符串反过来扩展KMP一次,求得结果与前面的意义相同,看代码吧,对了,可能性不能一个一个的加,肯定超时,我用了线段树,后来才发现从后面往前加简单的一塌糊涂,too young

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=100005;
int next[maxn],extand[maxn];
char S[maxn],T[maxn];
int ans1[maxn],ans2[maxn];
int num[maxn*4],num1[maxn*4];
void GetNext(const char *T){//扩展KMP模版
     int len=strlen(T),a=0;
     next[0]=len;
     while(a<len-1 && T[a]==T[a+1]) a++;
     next[1]=a;
     a=1;
     for(int k=2;k<len;k++){
         int p=a+next[a]-1,L=next[k-a];
         if( (k-1)+L >= p){
             int j = (p-k+1)>0 ? (p-k+1) : 0;
             while(k+j<len && T[k+j]==T[j]) j++;
             next[k]=j;
             a=k;
         }
         else
             next[k]=L;
     }
}
void GetExtand(const char *S,const char *T){
     GetNext(T);
     int slen=strlen(S),tlen=strlen(T),a=0;
     int MinLen = slen < tlen ? slen : tlen;
     while(a<MinLen && S[a]==T[a]) a++;
     extand[0]=a;
     a=0;
     for(int k=1;k<slen;k++){
         int p=a+extand[a]-1, L=next[k-a];
         if( (k-1)+L >= p){
             int j= (p-k+1) > 0 ? (p-k+1) : 0;
             while(k+j<slen && j<tlen && S[k+j]==T[j]) j++;
             extand[k]=j;
             a=k;
         }
         else
             extand[k]=L;
     }
}//模版结束
void pushdown(int node){//线段树求可能性,其实完全用不到,实力线段树.....
    if(num1[node]){
        num1[node<<1]+=num1[node];
        num1[node<<1|1]+=num1[node];
        num[node<<1]+=num1[node];
        num[node<<1|1]+=num1[node];
        num1[node]=0;
    }
}
void update(int l,int r,int le,int ri,int node){
    if(l<=le&&ri<=r){
        num[node]+=1;
        num1[node]+=1;
        return ;
    }
    pushdown(node);
    int t=(le+ri)>>1;
    if(l<=t) update(l,r,le,t,node<<1);
    if(r>t) update(l,r,t+1,ri,node<<1|1);
    num[node]=num[node<<1]+num[node<<1|1];
}
int query(int pos,int le,int ri,int node){
    if(le==ri) return num[node];
    pushdown(node);
    int t=(le+ri)>>1;
    int ans;
    if(pos<=t) ans=query(pos,le,t,node<<1);
    else ans=query(pos,t+1,ri,node<<1|1);
    return ans;
}
int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        memset(num,0,sizeof(num));
        memset(num1,0,sizeof(num1));
        scanf("%s%s",S,T);
        int len1=strlen(S);
        int len2=strlen(T);
        memset(ans1,0,sizeof(ans1));
        memset(ans2,0,sizeof(ans1));
        GetExtand(S,T);
        for(int i=0;i<len1;i++){
            if(extand[i]>=1)
            update(1,extand[i],1,len1,1);
        }
        for(int i=1;i<len1;i++) ans1[i]=query(i,1,len1,1);
        //利用线段树求母串匹配子串到i的可能性
        for(int i=0;i<len1/2;i++) swap(S[i],S[len1-i-1]);
        for(int i=0;i<len2/2;i++) swap(T[i],T[len2-i-1]);
        GetExtand(S,T);
        memset(num,0,sizeof(num));
        memset(num1,0,sizeof(num1));
        for(int i=0;i<len1;i++){
            if(extand[i]>=1)
            update(1,extand[i],1,len1,1);
        }
        for(int i=1;i<len1;i++) ans2[i]=query(i,1,len1,1);
        //反过来再求一遍母串匹配子串到i的可能性,但这两个的意义可不相同,一个从前匹配,一个从后面匹配
        long long ans=0;
        for(int i=1;i<len2;i++){//从1开始,如果这边等于1,为了匹配成功,后面的全要匹配,这样反过来在来一次扩展KMP就可以了
            ans+=(long long)((long long)ans1[i]*(long long)ans2[len2-i]);//理解了扩展KMP的意义后这里还是好理解的
        }
        printf("%lld\n",ans);
    }
    return 0;
}

你可能感兴趣的:(数据结构,KMP,ACM,ZOJ,扩展kmp)