KMP 学习笔记

从一个经典问题入手:

给定一个串 \(T\) 和一个串 \(S\),判断 \(S\) 是否为 \(T\) 子串。

这几天听了非常强的选手用自动机的角度思考了 KMP,虽然完全没有听懂,但确实感觉原来并没有理解 KMP。

先考虑一个串 \(S\)\(T\) 匹配。假设现在读进来了一个 \(T\) 的前缀 \(T_{1\sim i}\),要记录什么信息。

如果 \(S\) 作为 \(T\) 的子串,只可能有三种情况:

  • \(S\) 可以和 \(T\) 之前的一部分匹配,但和 \(i\) 没什么关系。

  • \(S\) 可以和一段 \(T\) 之后的部分匹配,也和 \(i\) 没什么关系。

  • \(S\) 可以和一段包含 \(T_i\) 的部分匹配,此时显然是读入进来的 \(T_{1\sim i}\) 的一个后缀匹配上了 \(S\) 的前缀。

考虑记录一个 \(len\) 代表 \(T_{1,i}\) 的后缀能匹配的 \(S\) 的最长前缀。

于是类似自动机里的“状态”就是这个 \(len\)

然后考虑转移。对于添加了一个字符 \(c\)

  • 相较于 \(T_{1,i-1}\) 的状态 \(len\),若添加的字符 \(c\) 恰好和 \(S\) 前缀 \(len+1\) 匹配上,则状态 \(len'=len+1\)

  • 若添加的字符 \(c\) 不能匹配上,则相当于我们要寻找一个短的,满足 \(T_{1,i-1}\) 的后缀能和 \(S\) 的前缀匹配上的位置,再尝试能否匹配上。若不能则再找。

考虑怎么找。

假设这里有两个 \(S\) 的前缀都能匹配上 \(T\)

假设短前缀为 \(s'\),因为 \(T\) 的后缀和能它匹配,所以 \(T\) 的后缀也是 \(s'\)

因为 \(S\) 的短前缀是 \(s'\),所以其和 \(T\) 匹配的长前缀对应 \(T\) 的后缀的前缀也是 \(s'\)

于是,这个问题可以简化成 \(S\) 和自己匹配:

\(p_i\) 表示 \(S_{1\sim i}\) 的这个前缀能够自己匹配的最大长度。

发现如果 \(p\) 知道了,每次找不到的时候直接跳到 \(p_i\) 去尝试匹配就行了。

考虑怎么处理 \(p\),和上面的过程其实是类似的,但是为了增加印象我还是重新写一遍吧。

考虑 \(p_{i}\) 怎么转移。

  • 若添加的字符 \(c\) 可以与 \(S_{p_i+1}\) 匹配上:


则直接匹配就行了。

  • 否则,需要找到一个可以和 \(S_{1\sim i}\) 的后缀匹配的,其实就是 \(S_{1\sim p_i}\) 这个前缀的一个后缀。

所以考虑 \(p_{p_i}\) 是否满足即可,不满足继续跳。

P3375 【模板】KMP

#include<bits/stdc++.h>
#define sd std::
//#define int long long
#define F(i,a,b) for(int i=(a);i<=(b);i++)
#define ff(i,a,b) for(int i=(a);i>=(b);i--)
#define me(x,y) memset(x,y,sizeof x)
#define pii sd pair<int,int>
#define X first
#define Y second
#define Fr(a) for(auto it:a)
#define dbg(x) sd cout<<#x<<":"<<x<<" "
#define dg(x) sd cout<<#x<<":"<<x<<"\n"
int read(){int w=1,c=0;char ch=getchar();for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;for(;ch>='0'&&ch<='9';ch=getchar()) c=(c<<3)+(c<<1)+ch-48;return w*c;}
void printt(int x){if(x>9) printt(x/10);putchar(x%10+48);}
void print(int x){if(x<0) putchar('-'),printt(-x);else printt(x);}
void put(int x){print(x);putchar('\n');}
void printk(int x){print(x);putchar(' ');}
const int N=1e6+10;
int n,m;
int p[N];//处理p[i]
char s[N],t[N];//判断s在t中出现的所有位置
void solve()
{
	scanf("%s%s",t+1,s+1);
	n=strlen(t+1),m=strlen(s+1);
	F(i,2,m)
	{
		int j=p[i-1];
		while(j&&s[i]!=s[j+1]) j=p[j];
		if(s[i]==s[j+1]) p[i]=j+1;
	}
	
	int j=0;
	F(i,1,n)
	{
		while(j&&t[i]!=s[j+1]) j=p[j];
		if(t[i]==s[j+1]) j++;
		if(j==m) put(i-m+1);
	}
	F(i,1,m) printk(p[i]);
}
int main()
{
	int T=1;
//	T=read();
	while(T--) solve();
    return 0;
}
posted @ 2025-08-25 18:39  _E_M_T  阅读(8)  评论(0)    收藏  举报