KMP 学习笔记
OI-wiki 上的内容。
这是学长讲的内容,我又重新整理了一遍。保证简单易懂。
字符串的若干记号
举例:
将
算法推导
先不用管怎么匹配,考虑求
首先显然有暴力求
注意,在本文代码中所有字符串下标都从
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
struct Hash{
const ll M=1e9+97;
const ll B=131;
string s;
int len;
ll pw[N],hsh[N];
void pre(){
len=s.size(),pw[0]=1,hsh[0]=s[0];
for(int i=1;i<=len;i++) pw[i]=pw[i-1]*B%M;
for(int i=1;i<=len;i++) hsh[i]=(hsh[i-1]*B+s[i-1])%M;
}
ll qry(int l,int r){
return (hsh[r]-hsh[l-1]*pw[r-l+1]%M+M)%M;
}
};
int fail[N];
int main(){
Hash h1;
cin>>h1.s;
h1.pre();
int n=h1.len;
for(int i=1;i<=n;i++){
printf("border of pre(%d):",i);
for(int j=1;j<i;j++)
if(h1.qry(1,j)==h1.qry(i-j+1,i))
printf(" %d",j),fail[i]=j;
puts("");
}
for(int i=1;i<=n;i++) printf("%d ",fail[i]);
return 0;
}
这是平方级别的,于是考虑优化。先来推到
这个非常显然。比如
其实也非常显然。
其实也不难理解。左边的一坨就是
举例,代入
所以,判断
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
struct Hash{
const ll M=1e9+97;
const ll B=131;
string s;
int len;
ll pw[N],hsh[N];
void pre(){
len=s.size(),pw[0]=1,hsh[0]=s[0];
for(int i=1;i<=len;i++) pw[i]=pw[i-1]*B%M;
for(int i=1;i<=len;i++) hsh[i]=(hsh[i-1]*B+s[i-1])%M;
}
ll qry(int l,int r){
return (hsh[r]-hsh[l-1]*pw[r-l+1]%M+M)%M;
}
};
vector<int> bd[N];
int main(){
Hash h1;
cin>>h1.s;
h1.pre();
int n=h1.len;
for(int i=1;i<=n;i++){
if(i!=1&&h1.s[0]==h1.s[i-1]) bd[i].push_back(1);
//注意特判长度为 1 的 boader。
//此外还需要判断 i=1 时不符合 boader 定义中 i<n 的情况。你可以把 i!=1&& 去掉试试。
for(int j:bd[i-1])
if(h1.qry(1,j+1)==h1.qry(i-(j+1)+1,i))
bd[i].push_back(j+1);
printf("border of pre(%d):",i);
for(int j:bd[i]) printf(" %d",j);
puts("");
}
return 0;
}
再次优化,发现上一个代码中 h1.qry(1,j+1)==h1.qry(i-(j+1)+1,i) 中有些比较完全是多余的,由于
接下来还有一个很重要的优化。
一个简单一些的解释:
这个其实也很显然:为了直观理解,下文令
于是我们只需要维护
举例:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
string s;
int fail[N];
int main(){
cin>>s;
int n=s.size();
s=" "+s;
for(int i=1;i<=n;i++){
for(int j=fail[i-1];j;j=fail[j])
if(s[j+1]==s[i]){
fail[i]=j+1;
break;
}
if(fail[i]==0&&s[1]==s[i]&&i>1) fail[i]=1;
}
for(int i=1;i<=n;i++) printf("%d ",fail[i]);
return 0;
}
然而有一种更简单的写法,也就是市面上大多数的板子:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
string s;
int fail[N];
int main(){
cin>>s;
int n=s.size();
for(int i=2,j=0;i<=n;i++){
while(j!=0&&s[j]!=s[i-1]) j=fail[j];
if(s[j]==s[i-1]) j++;
fail[i]=j;
}
for(int i=1;i<=n;i++) printf("%d ",fail[i]);
return 0;
}
模板
也就很简单了,将两个串拼接到一起,中间插入一个奇怪字符,如果某个位置的
例题
包括但不限于:
- P3375 【模板】KMP
- P4391 [BOI2009] Radio Transmission 无线传输
- CF126B Password
- UVA10298 Power Strings
- P3426 [POI2005] SZA-Template
- P2375 [NOI2014] 动物园
- P4824 [USACO15FEB] Censoring S
- CF526D Om Nom and Necklace
- UVA11022 String Factoring
- P3435 [POI2006] OKR-Periods of Words
- P6216 回文匹配
- CF808G Anthem of Berland
- P5829 【模板】失配树
由于我太菜了,故不能对每道题分别写题解。
一些其他用法
处理循环节
另
失配树
将
所以所有的