扩展 KMP(Z 函数)
扩展 KMP(Z 函数)
下文用 \([a, b]\) 表示 \(s[a \to b]\),\((l, r)\) 表示当前 \(r\) 最右的匹配段。
问题一
要解决的问题为:求出 \(z\) 函数,\(z(i) = \operatorname{LCP}(s[i, nS], s[1, nS])\),其中 \(\operatorname {LCP}(a, b)\) 表示 \(a, b\) 的最长相同前缀。
考虑到 \([l, r] = [1, r - l +1]\) 所以 \(\forall i \in [l, r]\) 有 \([i, r] = [i - l + 1, r - l + 1]\)。
因此,求 \(z(i)\) 可以利用到 \(z(i - l + 1)\),即 \(z(i) \geq \min\{ z(i - l + 1), r - i + 1\}\)。
分类讨论:
- \(i \leq r\)
- 若 \(z(i - l + 1) < r - i + 1\) 则 \(z(i) = z(i - l + 1)\);
- 若 \(z(i - l + 1) \geq r - i + 1\) 则令 \(z(i) = r - i + 1\) 然后往后扩展。
- \(i > r\) 则令 \(z(i) = 0\) 然后向后扩展。
由此可以写出代码:
nS = strlen(s + 1);
l = 0, r = 0;
for (int i = 2; i <= nS; ++ i)
{
if (i <= r && z[i - l + 1] < r - i + 1)
z[i] = z[i - l + 1];
else
{
z[i] = std::max(r - i + 1, 0);
while (i + z[i] <= nS && s[1 + z[i]] == s[i + z[i]])
z[i] ++;
}
if (i + z[i] - 1 > r)
l = i, r = i + z[i] - 1;
}
显然,外层循环只会循环 \(nS\) 次,而内层 while 循环每循环一次必使得 \(r\) 增大,\(r\) 顶多增大到 \(nS\),故内层循环顶多进行 \(nS\) 次。
综上所述,时间复杂度为 \(\mathcal O(n)\)。
问题二
我们要解决的问题为:求 \(b\) 与 \(a\) 每一个后缀的 \(\operatorname{LCP}\)。
把 \(b + a\) 拼起来做就可以了。