Skip to content

Commit 87e97f5

Browse files
committed
sync and update(long)
From 2025-11-15-1 on,maybe https://cnblogs.com/TH911 will has a more comfortable sense.
1 parent a64df09 commit 87e97f5

50 files changed

Lines changed: 11174 additions & 243 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

_posts/2024-11-18-2.md

Lines changed: 164 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -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
118261
pre[0]=-1;
119262
for(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
173310
for(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

Comments
 (0)