0%

P3375 【模板】KMP(前缀函数)(完全理解 kmp)

思路讲解

https://cp-algorithms.com/string/prefix-function.html

【KMP 学一遍忘一遍?ACM 金牌选手用可视化直击本质,理解了内核后想忘记都难!】 【精准空降到 04:05】 https://www.bilibili.com/video/BV1Er421K7kF/?share_source=copy_web&vd_source=6ca0bc05e7d6f39b07c1afd464edae37&t=245

那么我们容易发现,这个

image

ABC……ABC+D,那么如果前缀字符串后面跟着D,那么就是pi函数=4,但是如果后面不是D,那要怎么办?

不难发现,S[i+1]=S[π[i]+1]π[i+1]=π[i]+1S[i+1]=S[\pi[i]+1] \Rightarrow\pi[i+1]=\pi[i]+1(注意,这里我采用的是1-based,这更自然)。这不言而喻。(上面的这个图片已经解释了,我们再细化一下)

image

当然,这个是皆大欢喜的情况,但是如果出现失配了怎么办?

image

这个 pi_next(),为什么一定是这个 pi_next()。可以使用反证法

image

okay,问题就来到了,怎么样使用前缀数组实现这个文本串和模式串的这个匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//                  文本串             模式串
vector<ll> kmp(const string &s1,const string &s2){ // 返回总前缀数组pi
string S;
S='#'+s2+'#'+s1; // 采用1-based
N=SZ(S)-1;
vector<ll> pi(N+1,0);
FOR(i,1,N){
if(i==1) continue;
ll len=pi[i-1];
// if(S[i]==S[len+1]){
// pi[i]=len+1;
// }
while(true){
if(S[i]==S[len+1]){
pi[i]=len+1;
break;
}
if(len==0) break;
len=pi[len];
}
}
return pi;
}

特别的,我们认为 i=1 的时候 pi[i]=0。

他采用了 0-based,意思和我一样。

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

/*
* 采用 0-based 设计
* 模式串(s2)+'#'+文本串(s1) 需要这样子传入,如果想要执行 kmp 的话
* kmp 模板题 AC https://www.luogu.com.cn/record/269142391
*/
vector<ll> kmp(const string &A) {
// 返回总前缀数组pi
// 其定义是【1;i】真前后缀相同的最长长度
// 特别的,pi【1】=0
vector<ll> pi(SZ(A));
for (int i = 1; i < SZ(A); ++i) {
ll len = pi[i - 1];
while (len > 0 && A[len] != A[i]) {
len = pi[len - 1];
}
if (A[len] == A[i]) {
pi[i] = len + 1;
}
}
return pi;
}

void Solve() {
string s1, s2;
cin >> s1 >> s2;
vector<ll> pi = kmp(s2 + "#" + s1);
for (int i = SZ(s2); i < SZ(pi); ++i) {
if (pi[i] == SZ(s2)) {
ll ans = i - SZ(s2) - SZ(s2);
++ans;
cout << ans << "\n";
}
}
for (int i = 0; i < SZ(s2); ++i) {
cout << pi[i] << " ";
}
cout << "\n";
}

AC代码

AC
https://www.luogu.com.cn/record/269142391

心路历程(WA,TLE,MLE……)