扩展 KMP/exKMP/Z 函数 详解
约定:
- 本文中所有字符串下标均从 \(0\) 开始。
- 记 \(\vert s\vert\) 表示字符串 \(s\) 的长度。
- 记 \(s[l,r]\) 表示字符串 \(s_ls_{l+1}s_{l+2}\cdots s_r\)。
- \(s=t\) 表示字符串 \(s,t\) 相同。
Z 函数
定义
对于一个长为 \(n\) 的字符串 \(s\),定义函数 \(z_i\) 表示 \(s\) 和 \(s[i,n-1]\)(即以 \(s_i\) 开头的后缀)的最长公共前缀(LCP)的长度,则 \(z\) 被称为 \(s\) 的 Z 函数。特别地,\(z_0=0\)。
国外一般将计算该数组的算法称为 Z Algorithm,而国内则称其为 扩展 KMP。
过程
其实有点类似于 Manacher 的过程,和 KMP 关系反而不那么深刻(这也是为什么我喜欢叫它 Z 函数,它和 KMP 可能仅仅是求出的东西有点类似之处)。
记字符串为 \(s\),\(s\) 的 Z 函数为 \(z\)。
对于计算完成后的 \(z\),有 \(s[0,z_i]=s[i,i+z_i-1]\)。
称 \([i,i+z_i-1]\) 为 \(i\) 的匹配段,那么维护最靠右的匹配段 \([l,r]\)。
在计算 \(z_i\) 过程中,有 \(l\leq i\)。
在计算 \(z_i\) 过程中,如果 \(i\leq r\),则有:
因为 \(s[l,r]=s[0,r-l],s[l,i]=s[0,i-l]\),所以 \(s[i,r]=s[i-l,r-l]\)。(如图)

所以,\(z_i\geq\min(z_{i-l},r-i+1)\)。
随后,暴力扩展 \(z_i\) 即可。
扩展完之后,记 \(k=i+z_i-1\),若 \(k>r\),则 \(l\leftarrow i,r\leftarrow k\)。
代码
void Z(char a[],int z[]){
int n=strlen(a);
for(int i=1,l=0,r=0;i<n;i++){
if(i<=r){
z[i]=min(z[i-l],r-i+1);
}
while(i+z[i]<n&&a[z[i]]==a[i+z[i]]){
z[i]++;
}
if(i+z[i]-1>r){
l=i,r=l+z[l]-1;
}
}
}
时间复杂度
外层循环显然是 \(\mathcal O(n)\) 的。
而对于内层循环,考虑不会更新 \(r\) 的情况下,\(i+z_i\leq r\),又考虑到 \(r\) 单调不降,则内层 while 的时间复杂度为 \(\mathcal O(n)\)。
总时间复杂度:\(\mathcal O(n)\)。
例题:扩展 KMP
题中的 \(z\) 数组显然就是 Z 函数(特别地,\(z_0=\vert b\vert\),而不是 Z 函数定义的 \(0\)),但是 \(p\) 数组似乎有点不一样。
但是仔细思考一下就能发现,将 \(a\) 接到 \(b\) 的后面求字符串 \(ba\) 的 Z 函数 \(z'\) 即可求出答案,有 \(p_i=\min(\vert b\vert,{z'}_{i+\vert b\vert})\)。
记得开 long long。
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
constexpr const int N=2e7;
typedef long long ll;
char a[N+1],b[N<<1|1];
int z[N+1],p[N<<1|1];
void Z(char a[],int z[]){
int n=strlen(a);
for(int i=1,l=0,r=0;i<n;i++){
if(i<=r){
z[i]=min(z[i-l],r-i+1);
}
while(i+z[i]<n&&a[z[i]]==a[i+z[i]]){
z[i]++;
}
if(i+z[i]-1>r){
l=i,r=l+z[l]-1;
}
}//题目特殊要求
z[0]=n;
}
void P(char a[],char b[]){
for(int i=0;b[i];i++){
if(!b[i+1]){
for(int j=0;a[j];j++){
b[i+1+j]=a[j];
}
break;
}
}
Z(b,p);
}
int main(){
/*freopen("test.in","r",stdin);
freopen("test.out","w",stdout);*/
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>a>>b;
int sizeB=strlen(b);
Z(b,z);
P(a,b);
ll ansZ=0;
for(int i=0;i<sizeB;i++){
ansZ^=1ll*(i+1)*(z[i]+1);
}
ll ansP=0;
for(int i=0;a[i];i++){
ansP^=1ll*(i+1)*(min(sizeB,p[i+sizeB])+1);
}
cout<<ansZ<<'\n'<<ansP<<'\n';
cout.flush();
/*fclose(stdin);
fclose(stdout);*/
return 0;
}

浙公网安备 33010602011771号