【串串】Primitive Squares & 从另一个角度审视 Runs 相关理论
前前前言
此系列难以更新,因为我没那么智慧。
观前提示:
读者需要掌握 字符串基础、字符串哈希、少量周期理论(Periodicity Lemma),以及若干复杂度分析能力。
如果读者只希望学习或者见识一下其它可能的做法,不需要与成熟理论进行对照,则不需要掌握任何 Lyndon 和 Runs 相关理论。
摘要
本文提出了一种动态加入字符,并求出后缀 Primitive Squares(本原重串)的算法。单次修改均摊 \(O(\log^2n)\)。
同时,该算法可以较为简单地(?)在离线情况下以 \(O(n\log n)\) 的复杂度求出所有 Primitive Squares(该复杂度已达理论上界)。
遗憾的是,使用该算法计算 Runs 的复杂度依然为 \(O(n\log n)\),不够完美。
且该算法和 Runs 类似,偏向科技,应用空间不是很大。
前言
我说摘要一定要写在最上面不然没人看了。
如果这篇文章提到的算法已经被发明过,请将文章发给我,然后我将标题改为学习笔记。
笔者行文尚不熟练,若有格式或逻辑问题欢迎指出;笔者的 Lyndon 和 Runs 理论较学习较浅,不确定 Runs 是否支持动态在末尾添加字符同时计算出所有后缀Primitive Squares(目前我不会,如果你会了欢迎交流)。
正文
约定与记号
关于字符串部分的基础详见 OI Wiki 字符串,这里略提。
\(abc\) 表示拼接 \(a,b,c\) 三个字符串。
\(a^n\) 表示 \(n\) 个 \(a\) 拼接在一起。
若无特殊定义,下文的字符串 \(s\) 都是从 \(1\) 开始。
\(|s|\) 表示 \(s\) 的长度,\(s[i,j]\) 表示 \(s\) 的一个子串,第一个字符的标号为 \(i\),最后一个字符的标号为 \(j\)。
定义
- Squares:能表示成 \(x^2\) 的串,如 \(\texttt{abab}\)。
- Primitive Squares:不能再拆的 Squares,即最小周期为自身长度的一半的字符串。
\(\texttt{abab}\) 是 Primitive Squares,但 \(\texttt{aaaa}\) 不是。
引理
Three Squares Lemma
- 若对于一个字符串 \(S\),有 \(S[1,2a],S[1,2b],S[1,2c]\) 均为 Primitive Squares(\(0<a<b<c\leq \dfrac{|S|}2\)),则有 \(a+b<c\)。
证明留作一个练习。或者看看这篇文章。笔者仅使用 Periodicity Lemma 完成过证明,但是太长了就不放上来了。
显然,以某个固定位置为结尾的 Primitive Squares 子串数量级为 \(O(\log n)\)。
Squares Judgment
给定所有以某个位置为结尾的 Squares,高效判断哪些是 Primitive Squares。
- 以长度递增顺序判定,设上一个 Primitive Squares 长度为 \(2x\),当前需要判断的 Squares 长度为 \(2x'\)。若上一个 Primitive Squares 往左的最远重复距离为 \(k\),则当前 Squares 是 Primitive Squares 的充要条件为:\(\lfloor \dfrac k2 \rfloor\times x<x'\)。
往左的最远重复距离的定义为能扩展几个连续的完整循环节,如 \(S=\texttt{aabababa}\),其 \(T=S[5,8]\) 是一个 Primitive Squares,其对应的 \(k=3\)。
严谨地将 \(k\) 定义出来是:
很遗憾这个名字是我自己起的,所以可能有点格格不入。
证明方式只需要用众所周知的的 Periodicity Lemma(周期引理)即可。
另外判定引理还有一个有用的扩展结论:
- 对于某个字符串上任意两个长度均为 \(2x\) 的 Squares,若它们在原串上出现位置的交 \(\geq x\),则它们一定同时是或者不是 Primitive Squares。
算法
考虑如何在线地更新出以 \(i\) 为结尾的所有 Primitive Squares。
我们可以注意到,对于长度为 \(2x\) 的 Squares,其一定经过恰好两个相邻的、下标是 \(x\) 倍数的点 \(kx-x\) 与 \(kx\)(\(2\leq k\leq \dfrac{n}x\))。
此技巧在 NOI2016 优秀的拆分中亦有记载。
对于新加入的字符 \(S_i\),我们枚举 \(i\) 的因数 \(x\),计算 \(S[1,i-x]\) 与 \(S[1,i]\) 的最长公共后缀 \(d\)。
- 若 \(d=0\),则接下来 \(x\) 个位置一定不会出现长度为 \(2x\) 的 Squares,直接不管。
- 否则把 \(x\) 挂到 \(\max(i,i+x-d)\) 的位置。这是首个可能出现 Squares 的位置。
处理在 \(i\) 处挂着的 \(x\) 时,我们判断一下 \([i-2x+1,i]\) 是否是一个 Squares,并将其加入待判断序列。
把所有待判断序列中的长度升序排序,先判断是不是 Squares,再用 Squares Judgment 判定。不是 Primitive Squares 直接删去。
(实现精细可以忽略这一行)特别地,最后需要把满足 \(i\equiv x-1\pmod x\) 的所有 Primitive Squares 移出待判断序列,它们已经过期了。
这样我们就可以用均摊 \(O(\log^2n)\) 的复杂度在线求出了所有以 \(i\) 为结尾的 Primitive Squares。总复杂度 \(O(n\log^2 n)\)。
注意:vector 虽然不处于常数瓶颈,但实现时最好手写。
我在例题中提供了该算法的一种实现。
例题
给定字符串 \(S\),将其划分为若干非空连续子串 \(s_1,s_2,\ldots,s_k\),要求:
- 每个 \(s_i\) 是非循环的(不存在 \(k\geq 2\) 和字符串 \(t\) 使得 \(s_i=t^k\));
- 相邻子串不同(\(s_i\neq s_{i+1}\))。
求满足条件的划分总数,对 \(998244353\) 取模。
需要支持动态在末尾追加字符并在线输出。
即【串串划分】 的加强版。
转化
记字符串 \(s\) 的最小循环节长度为 \({\rm mnper}(s)\),最大循环次数为 \({\rm mxrep}(s)\)。注意这里有 \({\rm mnper}(s){\rm mxrep}(s)=|s|\)。
设 \(f_i\) 表示 \(S[1,i]\) 的划分数。
略去部分容斥和数学推导,可得转移式子:
注意到最后的转移式子的右半部分,只有 Primitive Squares 才可能有贡献,并且可以前缀和维护。
用上述算法在线求出 Primitive Squares 即可。复杂度 \(O(n\log^2n)\)。
code。(自觉强制在线后在原题的提交)
参考文献
- 从 Lyndon Words 到 Three Squares Lemma(博客园) 及其引用的参考文献。
致谢
感谢 Milmon、CarroT1212 对于题解版本的初稿进行了初步阅读。
感谢 hxhhxh 在首发前耐心帮我读一遍。

浙公网安备 33010602011771号