@@ -18,7 +18,150 @@ words:
1818---
1919
2020> [ 例题链接] ( https://www.luogu.com.cn/problem/P3375 )
21+ >
22+ > $\text{Upd on 2025/11/25}$:** 全文重写** 。~~ 以前写的什么鬼东西~~ 。
2123
24+ 本文中,字符串下标统一** 从 $1$ 开始** 。
25+
26+ 设字符串 $s$,$s[ i] $ 表示 $s$ 的第 $i$ 个字符,以 $s[ l,r] $ 表示 $s[ l] s[ l+1] \cdots s[ r] $ 构成的子串。
27+
28+ # 前缀函数
29+
30+ 设长度为 $n$ 的字符串 $s$,设其前缀函数 $\pi(i)$,满足 $\pi(i)$ 为 $s$ 的前缀 $s[ 1,i] $ 的最长 border 的长度。
31+
32+ 定义字符串 $s$ 的 border 定义为一个不等于 $s$ 的字符串 $t$,满足 $t$ 同时是 $s$ 的前缀和后缀。
33+
34+ 例如对于字符串 $\texttt{ABABA}$,有 $\pi(1)=\pi(2)=0,\pi(3)=1,\pi(4)=2,\pi(5)=3$。
35+
36+ 显然 $\pi(1)=0$。
37+
38+ ## 前缀函数的计算
39+
40+ 考虑 $\pi(i)$ 满足什么条件。
41+
42+ 维护指针 $j$,表示成功匹配到了 $s[ 1,j] =s[ i-j,i-1] $,且 $j$ 最大。
43+
44+ * 若 $s[ i] =s[ j+1] $,那么有 $\pi(i)=j+1$,令 $i\leftarrow i+1,j\leftarrow j+1$。
45+
46+ * 否则当 $s[ i] \neq s[ j+1] $ 时,令 $j\leftarrow\pi(j)$,这样可以保留一个最长的 border。
47+
48+ 如果一直找不到 $s[ i] =s[ j+1] $ 直到 $j=0$,则说明无法匹配,令 $i\leftarrow i+1$,跳过这一位。
49+
50+ ## 前缀函数计算的时间复杂度
51+
52+ 考虑 $i$ 单调不降,$j$ 增加的上界同 $i$,均为 $\mathcal O(n)$。故总时间复杂度 $\mathcal O(n)$。
53+
54+ # KMP 算法
55+
56+ 设模式串 $s$,文本串 $t$,在 $t$ 中匹配 $s$。
57+
58+ KMP 算法引入了一个概念——fail 指针(失配指针)。
59+
60+ 如图所示:
61+
62+ ![ ] ( /img/2024/11/008.webp )
63+
64+ 设指针 $i,j$,表示匹配到了 $t[ i] $,** 已经** 成功找到了 $t[ i-j+1,i] =s[ 1,j] $。
65+
66+ 设已经找到了 $t[ i-j,i-1] =s[ 1,j] $:
67+
68+ * 若 $t[ i] =s[ j+1] $,则代表** 这一位匹配成功** ,可以继续匹配,令 $i\leftarrow i+1,j\leftarrow j+1$。
69+
70+ * 否则当 $t[ i] \neq s[ j+1] $ 时,说明** 匹配失败** 。
71+
72+ 那么我们需要** 充分利用已经匹配过的信息** ,从而避免无用计算,提升算法效率。
73+
74+ KMP 算法设计 fail 指针 $\operatorname{fail}(j)$ 表示 $j+1$ 失配(成功匹配到 $j$)时,$j$ 应当跳到哪里。
75+
76+ 当 $t[ i] \neq s[ j+1] $ 时,令 $j\leftarrow\mathrm{fail}(j)$。
77+
78+ $\operatorname{fail}(j)$ 满足 $s[ 1,j-1] $ 的 border 最长。
79+
80+ 那么在 KMP 中,取 $\operatorname{fail}(j)=\pi(j)$,这可以保证匹配无误,保留一个最长的 border。
81+
82+ ***
83+
84+ 其实从这里也可以看出来,求前缀函数 $\pi(i)$ 的过程本质上也可以看作让 $s$ 自己匹配自己。
85+
86+ 时间复杂度同前缀函数分析,$\mathcal O(n+m)$,$n,m$ 为字符串长度。
87+
88+ ## 例题 AC 代码
89+
90+ > [ luogu P3375【模板】KMP] ( https://www.luogu.com.cn/problem/P3375 )
91+ >
92+ > 给定字符串 $s_1,s_2$,求 $s_2$ 在 $s_1$ 中所有出现位置和 $s_2$ 的前缀函数。
93+
94+ ``` cpp
95+ // #include<bits/stdc++.h>
96+ #include < algorithm>
97+ #include < iostream>
98+ #include < cstring>
99+ #include < iomanip>
100+ #include < cstdio>
101+ #include < string>
102+ #include < vector>
103+ #include < cmath>
104+ #include < ctime>
105+ #include < deque>
106+ #include < queue>
107+ #include < stack>
108+ #include < list>
109+ using namespace std ;
110+ constexpr const int N=1e6 ;
111+ int n,m,fail[N+1 +1 ];
112+ char s1[N+1 ],s2[N+1 ];
113+ int main (){
114+ /*freopen("test.in","r",stdin);
115+ freopen ("test.out","w",stdout);* /
116+
117+ ios::sync_with_stdio(false);
118+ cin.tie(0);cout.tie(0);
119+
120+ cin>>(s1+1)>>(s2+1);
121+ n=strlen(s1+1);m=strlen(s2+1);
122+ for(int i=2,j=0;i<=m;){
123+ if(s2[ i] ==s2[ j+1] ){
124+ fail[ i++] =++j;
125+ }else if(j==0){
126+ fail[ i++] =0;
127+ }else{
128+ j=fail[ j] ;
129+ }
130+ }
131+ for(int i=1,j=0;i<=n;){
132+ if(s1[ i] ==s2[ j+1] ){
133+ i++,j++;
134+ if(j==m){
135+ cout<<i-m<<'\n';
136+ j=fail[ j] ;
137+ }
138+ }else if(j==0){
139+ i++;
140+ }else{
141+ j=fail[ j] ;
142+ }
143+ }
144+ for(int i=1;i<=m;i++){
145+ cout<<fail[ i] <<' ';
146+ }
147+
148+ cout.flush();
149+
150+ /* fclose(stdin);
151+ fclose(stdout);* /
152+ return 0;
153+ }
154+ ```
155+
156+ ***
157+
158+ <details class =" info " >
159+ <summary>存档</summary>
160+ <p>
161+ 放一段以前的 Markdown 源码,想看的自己看看好了:
162+ </p>
163+
164+ ``` markdown
22165## 什么是 KMP 算法
23166
24167### 命名
@@ -45,7 +188,7 @@ words:
45188
46189朴素代码代码如下:
47190
48- ``` cpp
191+ ```cpp
49192//#include<bits/stdc++.h>
50193#include<algorithm >
51194#include<iostream >
@@ -84,23 +227,23 @@ int main(){
84227 fclose(stdout);* /
85228 return 0;
86229}
87- ```
230+ ```
88231
89232## KMP 算法
90233
91234### 策略
92235
93236先看看朴素算法的匹配策略:
94237
95- ![ ] ( /img/2024/11/007 .webp)
238+ ![ 007 ] ( https://img2024.cnblogs.com/blog/3541769/202507/3541769-20250720134015891-75142289 .webp)
96239
97240再看看 KMP 算法的匹配策略:
98241
99- ![ ] ( /img/2024/11/008 .webp)
242+ ![ 008 ] ( https://img2024.cnblogs.com/blog/3541769/202507/3541769-20250720134023529-932666720 .webp)
100243
101244<p style =" color : grey ;text-align : center ;font-size : 15px ;" >图片来源:见参考链接</p >
102245
103- 可以发现,KMP 算法在失配时“将模式串移到了适配位置的后方” 。
246+ 可以发现,KMP 算法在失配时”将模式串移到了适配位置的后方“ 。
104247
105248显然,我们不可能真的去这么做,因为太费时了。
106249
@@ -114,7 +257,7 @@ int main(){
114257
115258先上代码:
116259
117- ``` cpp
260+ ```cpp
118261pre[ 0] =-1;
119262for(int i=0,j=0;i<n;){
120263 if(j==-1||s1[ i] ==s2[ j] )i++,j++;
@@ -123,7 +266,7 @@ for(int i=0,j=0;i<n;){
123266 //匹配到了
124267 }
125268}
126- ```
269+ ```
127270
128271其中,$pre_0=-1$ 仅仅是一个特殊值(详见下文)。
129272
@@ -143,15 +286,9 @@ for(int i=0,j=0;i<n;){
143286
144287<!-- 不要修改此处的样式,这是为了适配 krmarkdown 特意制作的。Don't modify the style of this,it's working fine with krmarkdown,may not be fine when it works on other markdown-->
145288
146- 最开始长这样:
147- $$
148- \begin{aligned}&\texttt{CDA}\color{red}\texttt{CD}\color{black}\texttt{BCD}\\ &\texttt{CDB}\color{red}\texttt{CD}\end{aligned}
149- $$。
289+ 最开始长这样:$\begin{aligned}&\texttt{CDA}\color{red}\texttt{CD}\color{black}\texttt{BCD}\\ &\texttt{CDB}\color{red}\texttt{CD}\end{aligned}$。
150290
151- 就会把模式串位移成:
152- $$
153- \begin{aligned}\texttt{CDA}&\color{red}\texttt{CD}\color{black}\texttt{BCD}\\ &\color{red}\texttt{CD}\color{black}\texttt{BCD}\end{aligned}
154- $$ 。
291+ 就会把模式串位移成:$\begin{aligned}\texttt{CDA}&\color{red}\texttt{CD}\color{black}\texttt{BCD}\\ &\color{red}\texttt{CD}\color{black}\texttt{BCD}\end{aligned}$。
155292
156293可以发现:此时会存在** 公共部分** ($\color{red}\texttt{CD}$)。
157294
@@ -169,12 +306,12 @@ $$。
169306
170307先放代码:
171308
172- ```cpp
309+ ```cpp
173310for(int i=0,j=-1;i<m;){
174311 if(j==-1||s2[ i] ==s2[ j] )pre[ ++i] =++j;
175312 else j=pre[ j] ;
176313}
177- ```
314+ ```
178315
179316一个明显的事实:$i\geq j$ 恒成立。
180317
@@ -201,7 +338,7 @@ for(int i=0,j=-1;i<m;){
201338
202339## 例题 AC 代码
203340
204- ```cpp
341+ ```cpp
205342//#include<bits/stdc++.h>
206343#include<algorithm >
207344#include<iostream >
@@ -242,12 +379,19 @@ int main(){
242379 fclose(stdout);* /
243380 return 0;
244381}
245- ```
382+ ```
246383
247384## 参考链接
248385
249386< https://zhuanlan.zhihu.com/p/83334559 > (图片来源)
250387
251388< https://www.cnblogs.com/fswly/p/17959786 >
252389
253- <https://www.cnblogs.com/zzuuoo666/p/9028287.html>
390+ < https://www.cnblogs.com/zzuuoo666/p/9028287.html >
391+ ```
392+ </details >
393+
394+
395+
396+
397+
0 commit comments