定义

定义一个字符串 ss 的 border 为 ss 的一个ss 本身的子串 tt,满足 tt 既是 ss 的前缀,又是 ss 的后缀。

KMP 详解

kmpkmp 预处理的本质是在处理以每个位置 ii 为结尾的字符串,求最大的 borderborder

kmpkmp 是一种优化的暴力的思想。

暴力是 O(nm)O(nm)kmpkmpO(n+m)O(n+m)

其关键在于优化字符串的匹配。

考虑每次匹配,若 j+1j+1ii 可以匹配那么匹配,否则 jj 跳到上一个前后缀都是匹配的,后缀匹配是为了匹配文本串,前缀匹配时为了匹配自身,因此要这么做。

预处理模式串自身的 nene,首先对于第一个位置,ne[1]=1ne[1]=1,剩下的同理维护一个双指针,因为前后缀长度 +1+1 相当于时,前后缀的末尾同时匹配上一个新的,也就算是匹配了吧。

本质就是找到 [1,j][1,j][ij+1,i][i-j+1,i]ii 匹配的 jj

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
#include<iostream>
using namespace std;
int n,m;
const int N=1e6+8;
int ne[N];
void kmp(string s,int len){
for(int i=2,j=0;i<=len;i++){
while(j&&s[i]!=s[j+1]) j=ne[j];
if(s[i]==s[j+1]) j++;
ne[i]=j;
}
}
int main(){
string s,p;
cin>>s>>p;
n=s.length();
m=p.length();
s=" "+s;
p=" "+p;
kmp(p,m);
for(int i=1,j=0;i<=n;i++){
while(j&&s[i]!=p[j+1]) j=ne[j];
if(s[i]==p[j+1]) j++;
if(j==m){
printf("%d\n",i-m+1);
j=ne[j];
}
}
for(int i=1;i<=m;i++){
printf("%d ",ne[i]);
}
return 0;
}

例题

Compress Words

KMPKMP 板子。

每次合并合法区间,可以有效减少复杂度,为 O(len)O(\sum len)

Password

找到前缀,后缀,及既不是前缀又不是后缀的最长子序列。

考虑我们通过 kmpkmp 算法可以得知所有符合条件的,既是前缀又是后缀的序列长度,因此我们枚举中间 [2,n1][2,n-1] 满足于存在的既是前缀又是后缀的序列长度相等,就是答案。

P4391 [BOI2009] Radio Transmission 无线传输

KMPKMP 周期自然就是,nne[n]n-ne[n],证明画图。

扩展:

__END__