KMP 与 Z 函数拓展

【失配树:KMP 拓展】

先 KMP 一遍。然后对 0n 建立一棵树:nxt[i] 作为 i 的父结点。

则最长公共 border 就是这棵树上的 LCA 对应的长度。


border:若 a 既是 s 的前缀又是 s 的后缀,则 as 的 border。

周期:若 sp 为周期,则 s[i]=s[imodp]

整周期:若 sp 为整周期,则 sp 为周期且 pn

KMP 求出来的是 nxt 数组:nxt[i] 表示 s 的前 i 个字符构成的前缀的最长真 border 长度。

exKMP 求出来的是 Z 数组:z[i] 表示 s[0n1]s[in1] 的 LCP 长度。

R(s) 表示 s 无限循环构成的字符串。

root(s) 表示 s 的最小整周期字符串。

s 的 cyclic-shift 指 s 的循环移位。

【一些结论】

  1. 字符串 s 的所有 border 长度为 nxt[n],nxt[nxt[n]],

  2. s 有长度为 x 的 border snx 为周期。

  3. 如果 border 存在,最短 border 长度 n/2

  4. sp 为整周期 z[p]=np

  5. |root(s)|<|s| s 某个真 cyclic-shift 等于 s

  6. (nnxt[n])n,则 |root(s)|=nnxt[n];否则 root(s)=s

  7. s 分别以 p,q 为整周期,则 sgcd(p,q) 为整周期。(弱周期定理)

    证明:考虑 A=R(s)。有 Ai=Ai+p=Ai+q。由裴蜀定理,存在 ap+bq=gcd(p,q)。则 Ai=Ai+ap+bq=Ai+gcd(p,q)

  8. R(a)=R(b)ab=ba

    考虑引入第三个命题:root(a)=root(b)。我们尝试证明 R(a)=R(b)ab=ba 都与 root(a)=root(b) 等价。显然从 root(a)=root(b) 去推另外两个是很简单的。

    1. R(a)=R(b)root(a)=root(b)

      由结论 7,R(a)gcd(|a|,|b|) 为周期,则 a,b 都以 R(a) 的前 gcd(|a|,|b|) 个字符作为周期。

    2. ab=baroot(a)=root(b)

      根据 |a|+|b| 的长度归纳。显然 |a|+|b|=2 的时候成立。然后考虑 |a|>|b|,|a|=|b|,|a|<|b| 三种情况。|a|=|b| 显然。而 |a|<|b| 可以对称。因此只需要证明 |a|>|b| 情况。

      |a|>|b|ab=ba。不妨 a=bc,则 bcb=bbc。于是 cb=bc,由归纳法知 root(b)=root(c),而 a=bc 显然 root 也相等。

    于是证毕。

  9. R(a)<R(b)ab<ba

    考虑 ={09} 的情况(看作十进制数)。记 n=|a|,m=|b|

    左边可以看作两个循环小数的比较:0.aaaa...<0.bbbb....a×110n1<b×110m1

    右边可以写成十进制数:a×10m+b<b×10n+a

    可以发现这两个式子等价。拓展到更大的字符集也可以类似做,只是进制改变了。

【题目】

  • 判断两个字符串 s1,s2 是否循环同构。

    s=s1s1,判断 s 中是否有 s2 即可。跑一遍 KMP。

  • Periods of Words

    题意:若 as 的真前缀,且 saa 的前缀,称 as 的周期。给定字符串 w,求 w 每个前缀的最长周期长度之和(如果不存在算 0)。注意这个周期的定义和上面不是一个。

    容易发现,对于一个固定的 s,它最大周期等于 |s| 减去 s 的最短真 border 的长度(如果不存在真 border 则不存在周期)。

    因此跑一遍 KMP,可以求出每个前缀原始的 nxt。这里又有两种方法。

    1. 失配树找最高级祖先。

    2. 类似并查集路径压缩。因为如果当前位置的 nxt 不是这个位置最小的 nxt,那么任何时刻都不会用到这个位置的 nxt。于是每到一个新位置,直接暴力把当前位置的 nxt 一直递归到最小即可。

  • 寻找 t 的最长前缀是 s 的子串。

    SAM 直接冲

    我们可以用 KMP 这个优雅的算法。令 t 作为模式串,s 作为母串。

    观察得知,KMP 当匹配到母串的第 i 个位置时,实际上已经算出了 t 作为 s[1i] 的后缀的最大前缀 rho[i]

    答案就是 max{rho[i]}.

  • s 每个前缀的出现次数。

    SAM 直接冲

    1. KMP 方法。先对 s 做一遍 KMP。对于前缀 s[1i],只要看 nxt[i+1n] 里有多少个 nxt=i 即可。不要忘了加上自己开头出现了一次。

    2. Z 函数方法。先求出 Z 函数。对于前缀 s[1i],只要看 z[2n] 里有多少个 zi 即可。不要忘了加上自己开头出现了一次。

  • Extend to Palindrome

    简化题意:求最长回文后缀。

    t=rev(s)+#+s。对 tZ 函数,然后枚举每个后缀判断即可。

  • 求本质不同子串数。

    SAM 直接冲

    考虑增量算法:已经处理了字符串 s,在前面加一个字符 c,考虑以 c 开头的子串们。利用 nxt[]/z[] 去重。

    不过因为每新加入一个字符就要重新求一遍,复杂度 O(n2)

  • Pal-Palindrome

    结论:两个回文串 a,babPalroot(a)=root(b)

    那么弄一个类似桶的东西即可。

posted @   FLY_lai  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示