KMP 与 Z 函数拓展

【失配树:KMP 拓展】

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

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


border:若 \(a\) 既是 \(s\) 的前缀又是 \(s\) 的后缀,则 \(a\)\(s\) 的 border。

周期:若 \(s\)\(p\) 为周期,则 \(s[i]=s[i\bmod p]\)

整周期:若 \(s\)\(p\) 为整周期,则 \(s\)\(p\) 为周期且 \(p\mid n\)

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

exKMP 求出来的是 \(Z\) 数组:\(z[i]\) 表示 \(s[0\sim n-1]\)\(s[i\sim n-1]\) 的 LCP 长度。

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

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

\(s\) 的 cyclic-shift 指 \(s\) 的循环移位。

【一些结论】

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

  2. \(s\) 有长度为 \(x\) 的 border \(\iff\) \(s\)\(n-x\) 为周期。

  3. 如果 border 存在,最短 border 长度 \(\le n/2\)

  4. \(s\)\(p\) 为整周期 \(\iff\) \(z[p]=n-p\)

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

  6. \((n-nxt[n])\mid n\),则 \(|root(s)|=n-nxt[n]\);否则 \(root(s)=s\)

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

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

  8. \(R(a)=R(b)\iff 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)\Rightarrow root(a)=root(b)\)

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

    2. \(ab=ba\Rightarrow root(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)\iff ab<ba\)

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

    左边可以看作两个循环小数的比较:\(\overline{0.aaaa...}<\overline{0.bbbb....}\iff a\times \dfrac{1}{10^n-1}<b\times \dfrac{1}{10^m-1}\)

    右边可以写成十进制数:\(a\times 10^m+b<b\times 10^n+a\)

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

【题目】

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

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

  • Periods of Words

    题意:若 \(a\)\(s\) 的真前缀,且 \(s\)\(aa\) 的前缀,称 \(a\)\(s\) 的周期。给定字符串 \(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[1\sim i]\) 的后缀的最大前缀 \(rho[i]\)

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

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

    SAM 直接冲

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

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

  • Extend to Palindrome

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

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

  • 求本质不同子串数。

    SAM 直接冲

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

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

  • Pal-Palindrome

    结论:两个回文串 \(a,b\)\(ab\in Pal\iff root(a)=root(b)\)

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

posted @ 2024-08-05 09:58  FLY_lai  阅读(7)  评论(0编辑  收藏  举报