Z 函数(扩展KMP)

简介

定义一个长度为 \(n\) 的字符串 \(s\),定义 \(z_i\) 表示 \(s\)\(s[i,n]\)\(lcp\)(最长公共前缀),称 \(z\)\(s\)\(Z\) 函数。

朴素算法

暴力枚举,时间复杂度:\(O(n^2)\)

线性算法

我们首先考虑顺次处理 \(z_1,z_2,\dots,z_n\),当枚举到 \(i\) 时,也就是说 \(z_1,\dots,z_{i-1}\) 是已经求得了。

我们再来定义名词 Z-box :指与字符串 \(s\) 匹配的前缀区间,同时满足包含 \(i\) (当枚举到 \(i\) 时)且右端点最大。

首先我们来考虑如何更新这个 Z-box (分类讨论):

  1. \(l\) 为左端点(\(i-1\) 的 Z-box),\(r\) 为右端点(\(i-1\) 的 Z-box),\(i\) 为当前枚举的点。

  2. \(i>r\) 时,那也就是上一个 Z-box 对于 \(i\) 时没有用处的,那就从 \(i\) 开始,暴力向后匹配求得 Z-box

  3. \(i\le r\) 时,分两种情况:

    • \(z_{i-l+1}<r-l+1\)

      img

      那么 \(s[1,z[i-l+1]]=s[i-1+1,i-l+z[i-l+1]]=s[i,i+z[i-l+1]-1]\) (图标可能有点问题,以公式为准!!!)

    • \(z_{i-l+1}\ge r-l+1\)

      那么 \(s[1,r-l+1]=s[l,r]\),但是后面的匹配不能保证所以就向后匹配即可。

时间复杂度证明

枚举 \(i\)\(O(n)\) 的,\(r\) 会一直向后移动也是 \(O(n)\) 的,所以总的来说是 \(O(n)\) 的。

代码实现

inline void getz()
{
	for(int i=1;i<=m;i++)z[i]=0;
	z[1]=m;
	for(int i=2,l=0,r=0;i<=m;i++)
	{
		if(i<=r)z[i]=min(z[i-l+1],r-i+1);
		while(i+z[i]<=m&&b[i+z[i]]==b[z[i]+1])++z[i];
		if(r<i+z[i]-1)l=i,r=i+z[i]-1;
	}
}

同时这个也可拓展到两个串的匹配(\(s\)\(t\) 的每一个后缀的 \(lcp\)),代码实现:

inline void getext()
{
	for(int i=1;i<=n;i++)ext[i]=0;
	for(int i=1,l=0,r=0;i<=n;i++)
	{
		if(i<=r)ext[i]=min(z[i-l+1],r-i+1);
		while(i+ext[i]<=n&&b[ext[i]+1]==a[i+ext[i]])++ext[i];
		if(r<i+ext[i]-1)l=i,r=i+ext[i]-1;
	}
}
posted @ 2024-11-04 22:02  tyccyt  阅读(68)  评论(0编辑  收藏  举报