[补档计划] 覆盖子串与循环节
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[补档计划] 覆盖子串与循环节相关的知识,希望对你有一定的参考价值。
覆盖子串与循环节的概念
首先还是给覆盖子串和循环节下个定义.
为了方便后面的描述, 我们定义布尔记号 $P_S^T$ , 表示 $T$ 是否为 $S$ 的前缀. 对于一个定义, 为了对它有足够充分的了解, 可以通过多种形式描述这个定义, 这里就再引用图像的方法吧.
再引入前缀记号和后缀记号 pre[i] = S[1 : i], suf[i] = S[1 : |S|].
对于字符串 S , 若字符串 T , 满足 $P_S^T$ , 且 $\\exists k,~P_{kT}^S$ , 则称 T 为 S 的覆盖子串. 给了一般的定义, 我们需要考虑特殊情况, 一是以加深对定义的理解, 二是确定概念的完整性. 对于空串, 不可能是 S 的覆盖子串. 对于 S 本身, 可以作为 S 的覆盖子串.
既然引入了覆盖子串的定义, 我们顺其自然地可以定义最小覆盖子串: 对于长度最小的 T, 满足 T 为 S 的覆盖子串, 则 T 为 S 的最小覆盖子串, 记其为 Tmin. 特殊地, 0< |Tmin| <= |S| . 对于最小覆盖子串, 我们可以研究以下三个层次的问题: ①是否存在覆盖子串; ②最小覆盖子串的长度是多少; ③最小覆盖子串是什么. 发现关键是问题②, 问题①的答案是YES, 问题③可以根据问题②直接回答.
问题先不求解, 再给出循环子串的特殊化的定义, 循环节. 对于字符串 S , 若字符串 T , 满足 $P_S^T$ , 且 $\\exists k, ~s.t.~kT = S$ , 则称 T 为 S 的循环节, k 为 S 的循环节 T 的循环周期. 特殊的, S 本身为 S 的循环节, 循环周期为1.
类似地, 我们可以定义最小循环节和最大循环周期的概念. 同样的, 我们可以研究下面三个层次的问题: ①是否存在循环节; ②最小循环节的长度是多少, 最大循环周期是多少; ③最小循环节的长度是什么. 同样的, 关键还是问题②的最小循环节的长度.
概念间的关系如下:
next 数组的性质
既然概念弄清晰了, 我们就要找到覆盖子串, 循环节的性质. 在本节, 解决的是与 next 数组的一些关系, 进而更好地进行求解.
接下来的东西是根据我初一暑假的时候写的进行细化的, 又想起了当初黄厚大神对我的一番嘲讽, 现在看来那番嘲讽很有价值.
性质1 $pre({next}^k[n]) = suf({next}^k[n])$
证明
性质2 $pre(n-{next}^k[n])$ 一定为 S 的覆盖子串, S 的覆盖子串也一定形如 $pre(n-{next}^k[n])$ .
证明
$pre(n-{next}^k[n])$ 一定为 S 的覆盖子串:
S 的覆盖子串也一定形如 $pre(n-{next}^k[n])$ :
性质3 $pre(n-next[n])$ 为 S 的最小覆盖子串.
证明 ① 覆盖子串由性质2得知.
② 最小. 假设有更小, 推出 next 的矛盾.
性质4 若 $n-next[n]\\not | n$ , 则对于 ${next}^k[n]\\ne 0$ , $n-{next}^k[n]\\not | n$ .
证明 假设存在 k , 假如存在多个, 设 k 为最小的, 即循环节长度最小.
发现始终有 A = B , 所以存在更短的循环节, 矛盾.
循环节的性质
循环节还有更多的性质, 在本节进行阐述.
为了方便计算, 先弄清一条字符串数乘的运算律: a(bT) = (ab)T.
性质1 T 为 S 的循环节, 当且仅当存在 k , 使得 kT = S, 也使得 k|T| = |S| .
性质2 若 pre(A) 为 S 的循环节, 则对于任意 x , x | A , 有 pre(x) 为 S 的循环节.
证明 设 A = kx , 所以 S = p * pre(A) = p * [k * pre(x)] = (pk) * pre(x) .
性质3 若 pre(A), pre(B) 为 S 的循环节, 则 pre(gcd(A,B)) 为 S 的循环节.
证明 设 g = gcd(A,B), 所以存在 x, y, 使得 Ax + By = g , 所以 S[i] = S[i + Ax + By] = S[i+g] . 由于 g | B | n, 所以 pre(g) 为 S 的循环节.
利用字符串算法解决最小覆盖子串与循环节问题
最小覆盖子串
我们求出了 next[n] 后:
① Tmin 一定存在;
② 根据性质3, n - next[n] 就是 Tmin 的长度;
③ 由于 Tmin 是 S 的前缀, 所以 pre(next[n]) 为 Tmin.
最小循环节
方法1 利用 next 数组
我们求出了 next[n] 后.
若 n - next[n] 整除 n , 则 pre(n-next[n]) 为最小覆盖子串, 又由于 pre(n - next[n]) 为循环节, 循环节一定是覆盖子串, 所以 pre(n - next[n]) 为最小循环节.
若 n - next[n] 不整除 n , 由于所有循环节都可以表示为 n - next^k[n](性质2), 且 n - next^k[n] 不整除 n(性质4), 所以最小循环节长度为 n, 循环节为整个串.
我们还可以求出最大循环周期, 以及循环节具体是什么.
方法2 利用循环节的性质
或者还有第二种方法, 充分利用循环节的性质. 由于循环节的长度一定整除原串, 而原串是循环节, 所以尝试除去每个因数, 若仍然满足, 那么就除去, 最终得到的就是最小循环节的长度.
证明 假设求出的循环节长度为 A , 假设存在更小的循环节长度为 B , 则有 gcd(A,B) 为循环节长度.
由于 gcd(A,B) <= B , 而根据假设 B <= gcd(A,B) , 所以 B = gcd(A,B).
所以有 B | A , 而根据算法的过程, 若 B | A , 且 B 为循环节, 那么 A 可以找到 B.
综上, 矛盾, 所以最终可以得到最小的循环节长度.
那么, 对于循环节 pre(n) , x | n , 如何判断 pre(x) 是否为循环节呢? 我们可以通过 Hash , 判断两个 Hash 是否满足关系即可.
时间复杂度为 O(n + log n) .
next[N] 的求法
之前留了一个坑, 就是 next[n] 怎么求.
第一种方法, 是利用 KMP , 在 O(n) 求得 next[n].
第二种方法, 是利用字符串 Hash 来求解.
以上是关于[补档计划] 覆盖子串与循环节的主要内容,如果未能解决你的问题,请参考以下文章