Z算法
Z算法
Z算法是一种用于字符串匹配的算法。此算法的核心在于
(以下约定字符串下标从
z 数组和Z-box
定义
一个Z-box是一个区间。给定一个字符串
通俗来说,若从
例如若
z 数组的求法
给定字符串
由于
假设我们现在已经知道了
-
-
1. $i+z_{a,i'}\le zr$。显然$[i,i+z_{a,i'}]\subsetneq[zl,zr]$。根据Z-box的定义,$\forall j\in[i,i+z_{a,i'}],a_j=a_{j-zl+1}$。那么从$a$的第$i$位开始与$a$的前缀匹配的情况和从第$i'$位开始是一样的,直接令$z_{a,i}=z_{a,i'}$,$zl,zr$不变; 2. $i+z_{a,i'}>zr$。同理,$\forall j\in[i,zr],a_j=a_{j-zl+1}$。那么$a$的第$i\sim zr$位与$a$的前缀匹配的情况和第$i'\sim zr-zl+1$位是一样的,显然$z_{a,i}$至少有$zr-i+1$这么多,于是直接从第$zr+1$位开始暴力向后匹配求出$z_{a,i}$,并令$zl=i,zr=i+z_{a,i}-1$(因为$z_{a,i}$不可能为$0$)。
这样先令
下面是求
//|a|=n
void z_init(){//求z数组
z[1]=n;//特殊处理z[1]
int zl=0,zr=0;//右端点最大的Z-box
for(int i=2;i<=n;i++)//从i=2递推到i=n
if(zr<i){//第1种情况
z[i]=0;
while(i+z[i]<=n&&a[i+z[i]]==a[1+z[i]])z[i]++;//直接向后暴力匹配
if(z[i])zl=i,zr=i+z[i]-1;//更新右端点最大的Z-box
}
else if(i+z[i-zl+1]<=zr)z[i]=z[i-zl+1];//第2种情况的第1种情况
else{//第2种情况的第1种情况
z[i]=zr-i+1;//z[i]至少有zr-i+1这么多
while(i+z[i]<=n&&a[i+z[i]]==a[1+z[i]])z[i]++;//后面再暴力匹配
zl=i;zr=i+z[i]-1;//更新右端点最大的Z-box
}
}
时间复杂度
按上述方法求
证明(感性):观察上述方法可发现,只有当
Z算法的应用
Z算法和ExKMP算法是完全等价的,因为它们求的数组的意思是一样的。但是哈希、KMP能求的东西却有Z算法力所不及的。
Z算法最常用的用法就是字符串模式匹配(这个哈希和KMP也可以做到线性复杂度)。考虑把模式串
为什么Z算法在字符串模式匹配上花的时间和哈希相同呢?Z算法算出了从每一位开始能与前缀匹配的最长长度,但是字符串模式匹配只需要知道能否与前缀
不仅如此,Z算法的常数比哈希小(因为为了使哈希不被卡、不在CodeForces上FST,一般要写双重哈希),正确率也比哈希高(Z算法正确率当然是
3 道例题
CodeForces - 526D Om Nom and Necklace
题解传送门
CodeForces - 427D Match & Catch
题解传送门
CodeForces - 955D Scissors
题解传送门