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;
}

浙公网安备 33010602011771号