Z函数总结

记录了一个后缀和原串的LCP长度的信息。有点和后缀扯上关系了。

也有人叫它扩展KMP,但其实和KMP没有太大关系。

对于字符串\(s\),定义\(z_i\)表示\(suf_{s,i}\)\(s\)的最长公共前缀(LCP)的长度。我们可以\(O(n)\)求出\(z_i\)

类似KMP,我们同样递推,利用已经求出的信息降低复杂度。

根据定义,对于任意的\(i\)都有\(s_{1\dots z_i}=s_{i\dots i+z_i-1}\)。这类似于border,可以用来加速。

考虑维护最靠右的匹配段\(s_{i\dots i+z_i-1}\),记作\([l,r]\)。特别地,\(z_1=|s|\)没有意义,不算入匹配段中。于是从\(2\)开始,初始化\(l=r=0\)

计算\(z_i\)时,讨论一下:

  • \(i\le r\)\(s_{1\dots r-l+1}=s_{l\dots r}\Rightarrow s_{i-l+1,r-l+1}=s_{i,r}\),那么就有\(z_i\ge \min(r-i+1,z_{i-l+1})\),初始化后再暴力拓展。

  • \(i>r\),直接暴力拓展。

计算结束后更新\([l,r]\)即可。

看起来很暴力,但分析一下可以知道是\(O(n)\)的:

  • \(i>r\)时,暴力拓展一定使\(r\)变大。

  • \(i\le r\land z_{i-l+1}> r-i+1\)时,暴力拓展也会让\(r\)变大。

  • \(i\le r\land z_{i-l+1}\le r-i+1\)时,不会暴力拓展。

由于\(r\)最多从\(0\)变大到\(|s|\),所以复杂度是\(O(n)\)的。

  • 对于这道题luogu Z函数模板的第二问,用推导\(z\)函数的思路推一下即可。

  • 循环移位后与原串比大小:首先想到\(s\)\(t\)比大小只需比\(LCP(s,t)\)后的第一个字符即可。循环移位可以将字符串复制一份接在后面,然后上\(z\)函数推即可。

posted @ 2025-04-18 12:56  RandomShuffle  阅读(42)  评论(0)    收藏  举报