学习笔记/String:Z-Box 笔记
BASIC INFORMATION
TOPIC: \(\small\text{Z--Box}\) 算法。
REFERENCE: 基本说明和常用符号参考字符串系列目录&&说明。
UPDATE: 暂无
\(\text{Z}\) 算法,又称 \(\text{Z-Box}\) 算法,国内一般称为扩展 \(\text{KMP}\)。
其实我也没搞清楚它有什么别的算法做不到的功能,就当成增量法的思维练习吧(怎么又是你)。
对自身的 \(\text{Z-Box}\) 算法
\(\text{Z-Box}\) 算法,用于求出原串 \(s\) 与其从字符 \(s[i]\) 开始的后缀的最长公共前缀的长度 \(z[]\)(也称 \(\text{Z}\) 函数)。
给出形式化定义:即 \(z[i]=\operatorname{lcp}(s,s[i:])\),特别地,\(z[1]=0\)。
Question: 请写出 \(s=\texttt{abacaba}\) 的 \(z[]\)(\(\text{Z}\) 函数)。
Answer: \(z[]=\{0,0,1,0,3,0,1\}\)。
同样先看朴素做法:枚举 \(i\),暴力往后匹配即可,时间复杂度 \(O(n^2)\),给出实现:
// force.cpp
int n,z[N];
char s[N];
z[1]=0;
for(int i=2;i<=n;i++){
while(s[1+z[i]]==s[i+z[i]]) z[i]++;
}
考虑用增量法求出 \(z[]\),这一次我们可以利用的信息是 \(\operatorname{lcp}\) 带来的相同,类似 \(\text{Manacher}\),维护一个最大的匹配过的右端点 \(r\) 以及该右端点对应的对应的后缀的起始点 \(o\),接着考虑当 \(i\le r\) 时如何利用已知信息。
由 \(z[]\) 的定义有 \(s[:z[o]]=s[o:r]\),又 \(i \in [o,r]\),所以 \(s[i:r]=s[i-o+1:z[o]]=s[i-o+1:r-o+1]\),那就是说 \(s[i:]\) 的前 \(r-i+1\) 位与 \(s[i-o+1:]\) 的前 \(r-i+1\) 位相等,于是 \(z[i]\) 转换为 \(z[i-o+1]\),那么 \(z[i]\gets\min(z[i-o+1],r-i+1)\)。
\(\bm{p.s.}\) 与 \(r-i+1\) 取 \(\min\) 则是因为我们只知道它们前 \(r-i+1\) 位相等,但 \(z[i-o+1]\) 可能更多,但是第 \(r-i+1\) 位后面的我们是只能暴力判断的,因为我们没有匹配过 \(r\) 后面,所以要取 \(\min\)。
给出实现:
int n,z[N];
char s[N];
void Z_Box(){
z[1]=0;
for(int i=2,o=0,r=0;i<=n;i++){
z[i]=(i>r?0:min(z[i-o+1],r-i+1));
while(s[1+z[i]]==s[i+z[i]]) z[i]++;
if(i+z[i]-1>r) o=i,r=i+z[i]-1;
}
}
如果你对 \(\text{Manacher}\) 很熟悉的话,学习 \(\text{Z-Box}\) 应该如鱼得水,那么我们就基本把 \(\text{Z-Box}\) 解决了。
两个串之间的 \(\text{Z-Box}\) 算法
咕咕咕。