P3375 【模板】KMP(前缀函数)(完全理解 kmp) · znzryb
## 思路讲解
[https://cp-algorithms.com/string/prefix-function.html](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](https://www.bilibili.com/video/BV1Er421K7kF/?share_source=copy_web&vd_source=6ca0bc05e7d6f39b07c1afd464edae37&t=245)
那么我们容易发现,这个

ABC…..ABC+D,那么如果前缀字符串后面跟着D,那么就是pi函数=4,但是如果后面不是D,那要怎么办?
不难发现,$S[i+1]=S[\pi[i]+1] \Rightarrow\pi[i+1]=\pi[i]+1$(注意,这里我采用的是1-based,这更自然)。这不言而喻。(上面的这个图片已经解释了,我们再细化一下)

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

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

okay,问题就来到了,怎么样使用前缀数组实现这个**文本串和模式串**的这个**匹配**?
```cpp
// 文本串 模式串
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。


```cpp
/*
* 采用 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代码
::: 源代码
```cpp
// Problem: P3375 【模板】KMP
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3375
// Memory Limit: 512 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#define all(vec) vec.begin(),vec.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define pb push_back
#define SZ(a) ((int) a.size())
#define FOR(i, a, b) for (int i = (a); i <= (b); ++i)
#define ROF(i, a, b) for (int i = (a); i >= (b); --i)
#define debug(var) cerr << #var <<":"<<var<<"\n";
#define lson(var) (var<<1)
#define rson(var) ((var<<1)+1)
using namespace std;
using ll = long long;using ull = unsigned long long;
using DB=double;using LD=long double;
// using i128 = __int128;
using pdd = pair<DB,DB>;using plb = pair<ll,bool>;
using pll = pair<ll,ll>;
using arr3 = array<ll,3> ;using arr2 = array<ll,2>;
constexpr ll MAXN=static_cast<ll>(1e6)+10,INF=static_cast<ll>(1e17)+9; // 1e18+9也是素数
constexpr ll mod=static_cast<ll>(1e9)+7;
constexpr double eps=1e-8;
ll N,M,Q,X,K,lT,A[MAXN];
/*
*/
inline void init(){
}
// 文本串 模式串
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];
while(true){
if(S[i]==S[len+1]){
pi[i]=len+1;
break;
}
if(len==0) break;
len=pi[len];
}
}
return pi;
}
inline void solve(){
string s,s1;
cin>>s>>s1;
init();
vector<ll> pi=kmp(s,s1);
#ifdef LOCAL
FOR(i,1,N){
debug(i);
debug(pi[i]);
}
#endif
FOR(i,1,SZ(pi)-1){
if(pi[i]==SZ(s1)){
cout<<i-SZ(s1)*2<<"\n";
}
}
FOR(i,1,SZ(s1)){
cout<<pi[i]<<" ";
}
cout<<"\n";
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
// cin>>lT;
// while(lT--){
// solve();
// }
solve();
return 0;
}
/*
AC
https://www.luogu.com.cn/record/221413997
*/
```
:::
AC
[https://www.luogu.com.cn/record/269142391](https://www.luogu.com.cn/record/269142391)
::: 源代码
```cpp
/**
* Problem: P3375 【模板】KMP
* Contest:
* Judge: Luogu
* URL: https://www.luogu.com.cn/problem/P3375
* Created: 2026-03-23 13:08:20
* Author: Gospel_rock
* My blog: https://znzryb.com/
*
* Powered by AutoCp https://github.com/Pushpavel/AutoCp
*/
#include <bits/stdc++.h>
#define all(vec) vec.begin(),vec.end()
#define lson(o) (o<<1)
#define rson(o) (o<<1|1)
#define SZ(a) ((long long) a.size())
#define debug(var) cerr << #var <<" = ["<<var<<"]"<<"\n";
#define debug1d(a) \
cerr << #a << " = ["; \
for (int i = 0; i < (int)(a).size(); i++) \
cerr << (i ? ", " : "") << a[i]; \
cerr << "]\n";
#define debug2d(a) \
cerr << #a << " = [\n"; \
for (int i = 0; i < (int)(a).size(); i++) \
{ \
cerr << " ["; \
for (int j = 0; j < (int)(a[i]).size(); j++) \
cerr << (j ? ", " : "") << a[i][j]; \
cerr << "]\n"; \
} \
cerr << "]\n";
#define cend cerr<<"\n-----------\n"
#define fsp(x) fixed<<setprecision(x)
using namespace std;
using ll = long long;
using ull = unsigned long long;
using DB = double;
using i128 = __int128;
using CD = complex<double>;
static constexpr ll MAXN = (ll) 1e6 + 10, INF = (1ll << 61) - 1;
static constexpr ll mod = 998244353; // (ll)1e9+7;
static constexpr double eps = 1e-8;
const double PI = acos(-1.0);
ll lT, testcase;
/*
*
*/
/*
* 采用 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";
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
#ifdef LOCAL
cout.setf(ios::unitbuf); // 无缓冲流,方便我们调试
#endif
// cin >> lT;
// for (testcase = 1; testcase <= lT; ++testcase)
Solve();
return 0;
}
/*
*/
```
:::
## 心路历程(WA,TLE,MLE……)