扩展 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\),则有:

\[z_i\geq\min(z_{i-l},r-i+1) \]

因为 \(s[l,r]=s[0,r-l],s[l,i]=s[0,i-l]\),所以 \(s[i,r]=s[i-l,r-l]\)。(如图)

003

所以,\(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;
}
posted @ 2025-07-21 22:12  TH911  阅读(21)  评论(0)    收藏  举报