【小结】KMP
考虑一个问题:给定字符串 \(s,t\),在 \(s\) 中查找 \(t\) 的出现位置。
设 \(s\) 长度为 \(n\),\(t\) 长度为 \(m\)。
暴力匹配事件复杂度是:\(\mathcal O(nm)\) 的。
这个问题用哈希是可做的。可以做到 \(\mathcal O(n+m)\),但它在一些问题中没有 KMP 那么好的性质,而且也没有 \(100\%\) 正确性。
KMP 也可以在 \(\mathcal O(n+m)\) 的时间复杂度内以 \(100\%\) 的正确做对这个问题。
首先考虑 \(f_i\) 表示前 \(i\) 个字符,最大的 \(j\),使得 \(s[i-j+1\dots i]=t[1\dots j]\)。
注意到 \(j\leq i\)。
考虑怎么从 \(f_i\) 转移到 \(f_{i+1}\)。
如果下一个字符相同,直接接上一定更优。
否则,就每一次跳到前面的最大的 \(k<j\),使得 \(k\) 满足 \(s[i-k+1\dots i]=t[1\dots k]\)。
因为 \(k\) 是在不断减小的,所以 \(s[i-k+1\dots i]=t[j-k+1\dots j]=t[1\dots k]\)。我们发现 \(k\) 只和 \(j\) 有关。
考虑处理 \(nxt_i\) 表示 \(t[1\dots j]=t[i-j+1\dots i]\) 的最大 \(j\)。也称这个东西为最长 border。这个东西非常常用,考 KMP 主要考的是这个。
考虑如何处理它。
如果下一个字符相同,直接加 \(1\)。
否则,\(j\) 就一直往前跳,每一次跳到 \(nxt_j\) 的地方。直到实在不能跳了或者下一位相等了为止。
然后如果下一位相等了,再加 \(1\)。
此时 \(j\) 一定小于 \(m\),所以不用判断这个东西。
因为 \(nxt_1\) 不能判断第一个字符,所以直接处理 \(nxt_1=0\)。
然后就是处理 \(f\)。其实一样。不行就一直跳。注意 \(j=m\) 也要跳。因为要重新判断。
P3375 【模板】KMP
#include<bits/stdc++.h>
using namespace std;
/*
*/
struct FSI{
template<typename T>
FSI& operator >> (T &res){
res=0;T f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){res=res*10+ch-'0';ch=getchar();}
res*=f;
return *this;
}
}scan;
const int N=1e6+10;
int n,m,i,j;
char s[N],t[N];
int nxt[N];
int main()
{
scanf("%s%s",s+1,t+1);
n=strlen(s+1);
m=strlen(t+1);
nxt[1]=0;
for (i=2,j=0;i<=m;i++)
{
while (j>0&&t[j+1]!=t[i]) j=nxt[j];
if (t[j+1]==t[i]) j++;
nxt[i]=j;
}
for (i=1,j=0;i<=n;i++)
{
while (j==m||(j>0&&t[j+1]!=s[i])) j=nxt[j];
if (t[j+1]==s[i]) j++;
if (j==m) printf("%d\n",i-m+1);
}
for (i=1;i<=m;i++) printf("%d ",nxt[i]);
return 0;
}

浙公网安备 33010602011771号