跳到主要内容

Z 函数

参考资料

简介

对于长度为 nn 的字符串 ss,定义 Z 函数(Z-function)ziz_isss[in1]s[i\dots n-1] 的最长公共前缀(LCP)的长度,特别地 z0=0z_0=0。国内通常称计算该数组的算法为 扩展 KMP(exKMP)。

维护当前右端点最靠右的匹配段 [l,r][l,r],则 s[lr]s[l\dots r]ss 的前缀。计算 ziz_i 时:若 iri\le r,则 zimin(zil,ri+1)z_i\ge\min(z_{i-l},r-i+1);否则从头暴力扩展。每次扩展都使 rr 至少右移 1,总时间复杂度为 O(n)O(n)

实现

436 Bcpp
#include <bits/stdc++.h>
using namespace std;

const int N=500005;
int z[N];
void z_func(string s)
{
int n=s.size();
z[0]=0;
for(int i=1,l=0,r=0;i<n;i++)
{
z[i]=i<=r?min(z[i-l],r-i+1):0;
while(i+z[i]<n&&s[z[i]]==s[i+z[i]])z[i]++;
if(i+z[i]-1>r)l=i,r=i+z[i]-1;
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
string s;
cin>>s;
z_func(s);
int n=s.size();
for(int i=0;i<n;i++)cout<<z[i]<<' ';
return 0;
}

应用

模式匹配:求模式串 pp 在文本 tt 中的所有出现位置。取分隔符 #(不在两串中),对 p+#+tp+\#+t 求 Z 函数,凡 zi=pz_i=|p| 的位置都对应 tt 中的一次匹配。

最小循环节:枚举 ii,若 i+zi=ni+z_i=n,则 ss 的前 ii 个字符构成一个周期;最小的这样的 ii 即最小循环节长度。若还满足 nmodi=0n\bmod i=0,则 ss 由该循环节整数次重复而成。

例题

给定两个字符串 a,ba,b,你要求出两个数组:

  • bbzz 函数数组 zz,即 bbbb 的每一个后缀的 LCP 长度。
  • bbaa 的每一个后缀的 LCP 长度数组 pp

对于一个长度为 nn 的数组 aa,设其权值为 xori=1ni×(ai+1)\operatorname{xor}_{i=1}^n i\times(a_i+1)