KMP算法 学习笔记
概述
一种字符串匹配算法,是前缀函数的一个应用。
下文的字符串为 1-index。
前缀函数
对于字符串 \(s\),定义 \(\bold{Border}(s)\) 为对于 \(i \in \left[1, |s|\right)\),满足 \(pre_i=suf_i\) 的字符串 \(pre_i\) 的集合。\(\bold{Border}(s)\) 中的每个元素都称之为字符串 \(s\) 的 \(\operatorname{border}\)。也就是字符串的最长子串,满足它既是真前缀也是真后缀。
对于一个字符串,定义其前缀函数是一个数组 \(\pi\),\(\pi[i]\) 是前缀 \(pre_i\) 的最长 \(\operatorname{border}\) 的长度。
假如已知 \(\pi[i]\) 求 \(\pi[i+1]\)。如果 \(s_{\pi[i]+1}=s_{i+1}\),那么这两个字符可以与上一个 \(\operatorname{border}\) 拼接,则 \(\pi[i+1]=\pi[i]+1\)。否则设 \(pre_i\) 的第 \(n\) 长的 \(\operatorname{border}\) 长 \(j^{(n)}\)。如图,根据 \(\operatorname{border}\) 的定义,四段长 \(j\) 的子串相等,那么 \(j^{(2)}=\pi[\pi[i]]\)。同理 \(j^{(n)}=\pi[j^{(n-1)}]\)。
一直迭代 \(j\) 直到 \(s_{j+1}=s_{i+1}\) 或 \(j=0\),此时 \(j\) 就是 \(\pi[i+1]\)。
void getnxt(int m,char t[],int nxt[]){
nxt[1]=0;
for(int i=2,j=0;i<=m;i++){
while(j&&t[j+1]!=t[i])j=nxt[j];
if(t[j+1]==t[i])j++;
nxt[i]=j;
}
}
字符串匹配
KMP算法在失配时,不直接清空匹配位数,而是利用前缀函数保留一些匹配位数。
如图,假设匹配到主串的第 \(i\) 位,模式串的第 \(j\) 位。失配时,将模式串后移一个位置。四个小的方框表示 \(pre_j\) 的 \(\operatorname{border}\)。将 \(t\) 的真前缀移动到真后缀上,四个小的方框相等。
枚举 \(i\),因为要往后增加 \(i\),所以手动尝试匹配一位。如果失配,就不断缩小后移的位数。
int kmp(int n,char s[],int m,char t[],int nxt[],int ans[]){
int cnt=0;
for(int i=1,j=0;i<=n;i++){
while(j&&t[j+1]!=s[i])j=nxt[j];
if(t[j+1]==s[i])j++;
if(j==m)ans[++cnt]=i-m+1;
}
return cnt;
}
字符串匹配的代码与求前缀函数很像,求前缀函数可以看作模式串自己跟自己匹配。
复杂度证明:每次 \(j\) 可以一直减少,但是最多增加一,因此是 \(O(n+m)\)。
失配树
对于每个 \(i\) 连边 \(nxt_i\) 就形成了失配树。在失配树上,一个节点的所有祖先表示了一个前缀的所有 \(\operatorname{border}\)。
对于这道题,建出失配树。则 \(pre_u,pre_v\) 的最长公共 \(\operatorname{border}\) 就是 \(u,v\) 最近公共祖先。特殊情况是一个节点是另一个节点的祖先,由于 \(\operatorname{border}\) 不包含串本身,答案为最近公共祖先的父亲。
#include<bits/stdc++.h>
using namespace std;
char s[1000005];
int n,m,nxt[1000005],lg[1000005],dep[1000005],f[1000005][21];
vector<int>e[1000005];
void getnxt(int m,char t[],int nxt[]){
nxt[1]=0;
for(int i=2,j=0;i<=m;i++){
while(j&&t[j+1]!=t[i])j=nxt[j];
if(t[j+1]==t[i])j++;
nxt[i]=j;
}
}
int lca(int u,int v){
if(dep[u]<dep[v])swap(u,v);
while(dep[u]>dep[v])u=f[u][lg[dep[u]-dep[v]]];
if(u==v)return f[u][0];
for(int i=lg[dep[u]];i>=0;i--)if(f[u][i]!=f[v][i])u=f[u][i],v=f[v][i];
return f[u][0];
}
int main(){
cin>>s+1,n=strlen(s+1),getnxt(n,s,nxt);
for(int i=2;i<=n;i++)lg[i]=lg[i>>1]+1;
for(int i=1;i<=n;i++){
f[i][0]=nxt[i],dep[i]=dep[nxt[i]]+1;
for(int j=1;j<=lg[dep[i]]+1;j++)f[i][j]=f[f[i][j-1]][j-1];
}
cin>>m;
for(int i=1,u,v;i<=m;i++)cin>>u>>v,cout<<lca(u,v)<<'\n';
return 0;
}
[[字符串]]