A Secret HDU - 6153 扩展KMP || KMP

题目链接:https://vjudge.net/problem/HDU-6153
题意
求一个串T的所有后缀在串S中出现的次数.

给定母串str1,匹配串str2
str2[i-n]在str1中出现的次数为num[i]
str2[i-n]的长度为len[i]
\(∑(num[i]×len[i])\) (0<=i<=n,n为strlen(str2)-1) 。

扩展KMP解法

可以利用拓展KMP求出S的每一个后缀和T的最长公共前缀。
假如当前最长公共前缀为k,就说明长度为k的前缀在S中出现了一次,并且这个k前缀不能构成k+1前缀。用一个cnt数组将各种长度前缀出现的次数记录下来。

对于样例2

abababab
aba

首先将样例翻转,得到

babababa
aba

拓展KMP求出的extend数组值如下

extend[0] = 0
extend[1] = 3
extend[2] = 0
extend[3] = 3
extend[4] = 0
extend[5] = 3
extend[6] = 0
extend[7] = 1

所以cnt数组值为

cnt[1] = 1
cnt[2] = 0
cnt[3] = 3

根据cnt数组来求T的前缀在S中出现的次数:

  • 长度为3的前缀:出现了3次不解释。

  • 长度为2的前缀:在长度为3的前缀中出现过3次,不能构成3前缀的2前缀数目(即cnt[2])等于0,所以2前缀出现了3次。

  • 长度为1的前缀:在长度为2的前缀中出现了3次,不能构成2前缀的1前缀数目(即cnt[1])等于1,所以1前缀出现了4次。

问题解决

对于每一个点extend等差数列求和 累加就是答案:
因为每个点extend相当于
\(num[1]×len[1]+num[2]×len[2]+...+num[ extend[i] ]×len[ extend[i] ]\)
\(=1×1+1×2+...+1×extend[i]\)
\(=extend[i]×\frac{extend[i]+1}{2}\)

代码

#include <bits/stdc++.h>
using namespace std;
#define int long long 
const int N=1e6+10;
const int mod=1e9+7;
string s,t;
int slen,tlen;
int z[N];
int ext[N];
int cnt[N];
void get_Z(){
    int l=0,r=0;
    z[0]=tlen;
    for(int i=1;i<tlen;i++){
        if(i>=r){
            while(i+z[i]<tlen && t[i+z[i]]==t[z[i]])
                z[i]++;
            l=i,r=i+z[i];
        }
        else if(z[i-l]<r-i) 
                z[i]=z[i-l];
        else{
            z[i]=r-i;
            while(i+z[i]<tlen && t[i+z[i]]==t[z[i]])
                z[i]++;
            l=i,r=i+z[i];
        }
    }
}
void get_exKMP(){
    int l=0,r=0;
     while (r < slen && r < tlen && s[r] == t[r])
        r++;
    ext[0] = r; 
    // r=0;
    for(int i=1;i<slen;i++){
        if(i>r){
            while(i+ext[i]<slen && ext[i]<tlen && s[i+ext[i]]==t[ext[i]])
                ext[i]++;
            l=i,r=i+ext[i];
        }else if(z[i-l]<r-i) ext[i]=z[i-l];
        else {
            ext[i]=r-i;
            while(i+ext[i]<slen && ext[i]<tlen && s[i+ext[i]]==t[ext[i]])
                ext[i]++;
            l=i,r=i+ext[i];
        }
    }
}

void solve(){
    memset(z,0,sizeof z);
    memset(ext,0,sizeof ext);
    cin>>s>>t;
    slen=s.length(),tlen=t.length();
     reverse(t.begin(),t.end());
    reverse(s.begin(),s.end());
    get_Z();
    get_exKMP();

    int ans=0;
    for(int i=0;i<slen;i++){
         ans=(ans+(ext[i]*(ext[i]+1)/2)%mod)%mod;   
    }
    cout<<ans<<endl;
}


signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t=1;
    cin>>t;
    while(t--)
        solve();
    return 0;
}

KMP解法

KMP:和拓展KMP相似。
主要思路就是求出不能构成k + 1前缀的k前缀数目。
用KMP统计T串在S串中出现次数 的代码相似,KMP匹配的时候,我们只需要在失配和匹配完成的时候记录一下即可。
设失配的时候已经匹配的长度为k,那么这个k前缀不能构成k + 1前缀。

代码

#include <bits/stdc++.h>
using namespace std;
#define int long long 
const int N=1e6+10;
const int mod=1e9+7;
string s,t;
int slen,tlen;
int ne[N];
int cnt[N];
void get_next(){
    ne[0]=-1;
    int pre=-1,j=0;
    while(j<tlen){
        //不能使用优化
        if(pre==-1 || t[pre]==t[j]){
            ne[++j]=++pre;
        }
        else pre=ne[pre];
    }
}
void kmp(){
    get_next();
    int i=0,j=0;
     // 注意不能是<,否则最后一个模式会遗漏
     //可以模拟第二个样例得出
    while(i<=slen){
        if(j==-1 || s[i]==t[j])
            i++,j++;
        else {
            cnt[j]++;
            j=ne[j];
        }
        if(j==tlen) {
            cnt[j]++;
            j=ne[j];
        }
    }
}
void solve(){
    memset(cnt,0,sizeof cnt);
    cin>>s>>t;
    slen=s.length(),tlen=t.length();
    reverse(t.begin(),t.end());
    reverse(s.begin(),s.end());

    kmp();
    int ans=0;
    for(int i=tlen;i>0;i--){
        cnt[i]=(cnt[i]+cnt[i+1])%mod;
        ans=(ans+cnt[i]*i)%mod;
    }
    cout<<ans<<endl;
}


signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t;cin>>t;
    while(t--)
        solve();
    return 0;
}

原文:
https://blog.csdn.net/ECNU_LZJ/article/details/77477204

posted @ 2022-09-05 20:12  kingwzun  阅读(39)  评论(0编辑  收藏  举报