【小结】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;
}
posted @ 2026-08-21 16:45  GUO120822  阅读(2)  评论(0)    收藏  举报