如你所见,这是我不知道第几次学KMP了。
推荐B站上电子科大的字符串专题。

引入:KMP是干什么的

KMP解决的是模式串P在源串T中出现次数的问题,比如模式串P为aba,源串为abababa,我们可以求出计算重叠的出现次数3,还可以求出不计算重叠的出现次数2。

next数组

  • 最好不要用next命名next数组,某些OJ会报错
前(后)缀和真前(后)缀:字符串s前i个(i<=strlen(s))字符为其前缀,i!=strlen(s)时为真前缀,后缀和真后缀同理。

next[i]表示模式串P以i为尾的这个前缀,最长的公共真前缀和真后缀长度,例如abcabc,next[1:6]={0,0,0,1,2,3}

求next数组

假设我们已经知道next[1:i-1],求next[i]。

设last=next[i-1],则p[1:last]等于p[i-last:i-1],即模式串长度为i-1的前缀,前last个与后last个相同,且last最大,那么我们只需要检测p[i]与p[last+1]是否相等,相等就是last+1,否则要在这last个(p[i-last:i-1],即p[1:last])里面找,更新last=next[last],继续检测p[i]与p[last+1]是否相等,如此循环直到last=0,p[i]=p[1],即为1,否则为0。

那么只需要设置next[1]=0,循环求即可。

真正的KMP

通过求next数组,我们发现:next数组的作用是当前面的匹配好了,而下一个匹配不到时,更新一个更小的来匹配,取代了重新匹配,来加速匹配。

KMP的过程与求next的过程几乎完全相同。

我们设last为匹配到T[i-1]时当前已经匹配的个数,即当last=strlen( P )时,匹配成功,现在求匹配T[i]时的last。

我们已经匹配了last个,p[1:last]等于t[i-last:i-1](是不是很熟悉),如果p[i]与t[last+1]相同,last++,否则跳转到前面,last=next[last],尝试更小的匹配,直到完全不能匹配。

代码

作用:输出有多少次匹配,可重叠。

由于通常的输入从0开始,这个代码的next数组从0到n-1,每一位比正常的next数组少1。

回到开头的问题,如果想求的匹配不重叠,后面每当匹配到,last=0即可。

#include<bits/stdc++.h>
using namespace std;
int n,last;
char t[1000020],p[1002000];
int nex[1002000];
int main(){
    scanf("%d",&n);
    while(n--){
        int ans=0;
        scanf("%s%s",p,t);
        int pl=strlen(p),tl=strlen(t);
        nex[0]=last=-1;
        for(int i=1;i<pl;i++){
            while(last>-1&&p[i]!=p[last+1]){
                last=nex[last];
            }
            if(p[i]==p[last+1]){
                last++;
            }
            nex[i]=last;
        }
        last=-1;
        for(int i=0;i<tl;i++){
            while(last>-1&&t[i]!=p[last+1]){
                last=nex[last];
            }
            if(t[i]==p[last+1]) last++; 
            if(last+1==pl){
                ans++;
                last=nex[last];
                //printf("%d\n",i-pl+2);
            }
        }
        /*for(int i=0;i<pl;i++){
            //if(i!=0) printf(" "); 
            //printf("%d",nex[i]);
        }*/
        printf("%d\n",ans);
    }
    return 0;
}