[学习笔记] 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;
}