[学习笔记] kmp

不知道开头写什么话

康康下面这个问题:

给定一个模式串和一个文本串,求模式串在文本串中出现的位置,次数.

一个显然的做法是对于文本串中的每个位置,都把模式串从头开始匹配.

但是这样的时间复杂度太高了,

所以我们有了接下来要讲的:kmp算法.

step 0

先定义一些东西.

\(a\):模式串.

\(b\):文本串.

\(la\):\(a\)的长度.

\(lb\):\(b\)的长度.

\(next[i]\):模式串中满足\(a[1\sim j]=a[i-j+1\sim i]\)的最大的\(j\).

\(f[i]\):满足\(a[1\sim j]=b[i-j+1\sim i]\)的最大的\(j\).

step 1

首先暴力的算法因为时间复杂度太高(\(O(la*lb)\))而被抛弃...

所以我们要想想怎么排除掉一些多余的情况.

先举个例子:

\(a\):bababb.

\(b\):bababababababababb.

我们在第一次匹配的时候会是这样:

babab|b

babab|ababababababb

在第六位的时候失配了..

但是我们可以发现,前五位的\(f[i]\)都已经求出来了.

如果我们采取暴力的做法,将\(a\)直接往后移一位时,就是这样:

|bababb

b|ababababababababb

可以发现在第一位就失配了,

所以对第六位没有任何帮助.

于是我们可以想想,

要怎样跳才能一步跳到有用的位置.

这时我们就需要这个next数组.

step 2

(首先next数组的定义我已经放上面了...)

假如我们已经求出了next数组(先别管怎么求的).

当我们匹配完第\(i\)位,且第\(i\)位在模式串中对应的是\(j\),

(就像上面的例子中,\(i\)=5,\(j\)=5)

而在第\(i\)+1位失配了.

于是我们就要改变\(j\),使\(i+1\)能匹配上.

而改变的方式就是把\(j\)改为\(next[j]\).

想一想这是为什么.

假设有一个\(k>next[j]\),使\(a[1\sim k+1]=b[i+1-(k+1)+1\sim i+1]\).

(因为我们这里是在求\(i\)+1,而\(j\)是与\(i\)匹配,所以\(k\)加了1)

\(k\)对于\(next[j]\)能使答案更优,

因为\(i\)是匹配上了的,所以\(a[1\sim j]=b[i-j+1\sim i]\).

因为\(i+1\)\(k+1\)匹配上了,所以\(i\)\(k\)也能匹配上,

\(a[1\sim k]=a[i-k+1\sim i]\).

因此我们可以发现\(a[1\sim k]=a[j-k+1\sim j]\).

画个图理解一下(前面的不理解的可以自己画下图):

红色区域代表匹配了的部分.

图片过于丑陋还请原谅.

回到正题,根据\(next[j]\)的定义,这样的\(k\)是不存在的(否则\(next[j]\)就等于\(k\))

因此直接跳到\(next[j]\)肯定是更优的.

但是,\(next[j]\)怎么求呢?

step 3

仔细康康\(next\)\(f\)的定义,是不是非常像?

没错,我们就可以像求\(f\)一样,直接求\(next\).

就相当于自己与自己匹配一次.

code:

inline void getnext(){
    Next[1]=0;
    for(int i=2,j=0;i<=la;i++){
        while(j>0&&a[i]!=a[j+1]) j=Next[j];
        if(a[i]==a[j+1]) j++;
        Next[i]=j;
    }
}

所以,这个问题就解决了!

总的代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#define fre(x) freopen(x".in","r",stdin),freopen(x".out","w",stdout)
using namespace std;

inline int read(){
    int sum=0,f=1;char ch=getchar();
    while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
    return f*sum;
}

const int N=1000005;
int T,la,lb,f[N],Next[N],ans;
char a[N],b[N];

inline void getnext(){
    Next[1]=0;
    for(int i=2,j=0;i<=la;i++){
        while(j>0&&a[i]!=a[j+1]) j=Next[j];
        if(a[i]==a[j+1]) j++;
        Next[i]=j;
    }
}

inline void kmp(){
    for(int i=1,j=0;i<=lb;i++){
        while(j>0&&(j==la||b[i]!=a[j+1])) j=Next[j];
        if(b[i]==a[j+1]) j++;
        f[i]=j;if(f[i]==la) ans++;
    }
}

signed main(){
    T=read();
    while(T--){
        scanf("%s%s",a,b);
        la=strlen(a),lb=strlen(b);
        for(int i=la;i>=1;i--) a[i]=a[i-1];
        for(int i=lb;i>=1;i--) b[i]=b[i-1];
        getnext();kmp();        
        printf("%d\n",ans);ans=0;
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/zsq259/p/11745658.html
今日推荐