“$\text{Plan Z never fail!}$”
Z 函数
对于一个长度为 $len$ 的字符串 $s$ ,有一个长度为 $len$ 的数组称之为 $z$ ,其中 $z[i]$ 表示 $s[1..len]$ 与 $s[i..len]$ 的最长公共前缀 $LCP$ ,那么 $z$ 被称为 $s$ 的 $z$ 函数。
用文字叙述,即是 $z[i]$ 表示的是字符串 $s$ 与 $s$ 以 $i$ 开头的后缀字串的最长公共前缀长度。
一些需要了解的
在有些领域,有这样一个特殊定义 $z[1]=0$ ,即 $s[1..len]$ 与 $s[1..len]$ 的 $LCP$ 值为 $0$ 。但是,以普遍理性而言,这个定义是赘余的,但是,对于国际上现在通用的求 $z[]$ 的办法,对于求 $z[i]$ ,我们需要使用到 $z[0..i-1]$ 。而其中,所需要的,即是 $z[0]=0$ 。但是,在洛谷上的模板题,需要记入的答案是 $z[1]=length(s)$ 。那么,挺玄学的。
暴力求解
时间复杂度 $O(n^2)$ 。当然就是每一个后缀都一一比较即可。
线性时间求解
这个算法,国外称之为 $Z\ Algorithm$ ,国内则更偏向于 $Kmp.Ex$ 。原因在于此算法的做法和用处都与 $Kmp$ 相类似。
我们给出一个新的定义称为 Z-box ,对于一个 $i$ 而言,我们将区间 $[i,i+z[i]-1]$ 称为是 $i$ 的匹配段,即 Z-box 。
在求解 Z 函数时,我们需要维护右端点最右值的匹配段。为了方便,记作区间 $[l,r]$ 。因为区间 $[l,r]$ 是匹配段,那么, $s[l..r]$ 肯定是 $s$ 的前缀子串。在计算 $z[i]$ 时,我们需要保证 $l\le i$ 。且初始时, $l=r=0$ 。
在 $z[i]$ 的计算过程中具有:
如果 $i\le r$ ,那么根据 $[l,r]$ 的定义有 $s[i,r]=s[i-l,r-l]$ ,因此 $\min(z[i-l+1],r-i+1)\leq z[i]$ 。这时:
- 若 $z[i-l]\leq r-i+1$ ,则 $z[i]=z[i-l]$ 。
- 否则 $z[i-l]\geq r-i+1$ ,这时令 $z[i]=r-i+1$ ,然后暴力枚举下一个字符扩展 $z[i]$ 直到不能扩展为止。
如果 $i>r$ ,则从 $s[i]$ 开始暴力求 $z[i]$ 。
- 求出 $z[i]$ 之后,如果 $i+z[i]-1>r$ ,就更新 $[l,r]$ ,令 $l=i,r=i+z[i]-1$ 即可。
复杂度分析
内层函数满足将 $r$ 向后移,而 $r<n-1$ ,共执行 $n$ 次。外层线性遍历,同样是 $n$ 次,总时间复杂度 $\mathcal O(n)$ 。
应用
详见 OI Wiki
洛谷模板题
1 |
|