Z 函数 / 扩展 KMP

Z 函数 / 扩展 KMP

前置

  1. KMPO(n) 求解字符串匹配的算法。维护前缀数组 pi 表示字符串 si 结尾的最长公共前后缀的长度;
  2. border: 对于字符串 s,如果存在一个子串 t 满足 t 既是 s 的一个前缀又是 s 的后缀,则称 ts 的一个 border;

Z函数

定义:zi 表示字符串 s 下标从 i 开始的后缀与 s 的最长公共前缀 (LCP) 的长度。这里我们规定字符串下标从 0 开始。

e.g.

 s:a a a a b a a b

 i: 0 1 2 3 4 5 6 7

zi:8 3 2 1 0 2 1 0

注意,在处理不同的问题时 z0 通常为 |s|0

对于字符串 s 的下标 i,如果有 Zi0,那么称区间 [i,i+zi1] 为一个 Zbox(匹配段)。

考虑如何实现求解Z函数。假定区间 [l,r] 为找到的 Zboxr 最靠右的哪一个匹配段,当前在考虑第 i 个位置。

  • i>r,则从下标 0 开始暴力匹配;
  • ir
    • izil1<r,那么有 zi=zil
    • izil1r,暴力匹配

在每找到一个一个 Zbox,更新 l=i,r=i+zi1

单次暴力扩展至少使右端点 +1,故暴力匹配的总时间是 O(n) 的。

对于 izil1<r 的情况。由于 [l,r] 是一个 Zbox,所以 s[0,rl]=s[l,r],同时因为 lir,有 s[il,rl+1]=s[i,r]。这时候 zi 就相当于是 zil,直接转移即可。

总时间复杂度 O(n)

void Solve(string s) {
  z[0] = n = s.size();
  for(int i = 1, l = 0, r = 0; i < n; i++) {
    if(z[i - l] < r - i + 1) {
      z[i] = z[i - l];
    } else {
      for(z[i]= max(0, r - i + 1); i + z[i] < n && s[z[i]] == s[i + z[i]]; z[i]++) { // 暴力扩展
      }
      l = i, r = i + z[i] - 1;
    }
  }
}

应用

  1. 给定字符串 s,t,求出 s 的每一个后缀与 t 的最长公共前缀。

    sol: 构造字符串 t++s,然后求z函数即可。

  2. 求字符串 s 的所有 border

    sol: 对于每一个位置 i,如果有 i+zi=|s|,则当前以 i 开头的 Zbox 是一个 border

  3. 求字符串 s 的每一个前缀的出现次数。

    sol: 对 zi 的长度维护后缀和。因为对于位置 i,若 zi0,则长度在 [1,zi] 内的前缀均出现了一次。

练习:

P5410 【模板】扩展 KMP/exKMP(Z 函数)

CF432D Prefixes and Suffixes

UVA11022 String Factoring

UVA11475 Extend to Palindrome

CF535D Tavas and Malekas

完结撒花 \ /\ / \ / \ /

posted @   ereoth  阅读(45)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
点击右上角即可分享
微信分享提示