【串串】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\),则当前 SquaresPrimitive Squares 的充要条件为:\(\lfloor \dfrac k2 \rfloor\times x<x'\)

往左的最远重复距离的定义为能扩展几个连续的完整循环节,如 \(S=\texttt{aabababa}\),其 \(T=S[5,8]\) 是一个 Primitive Squares,其对应的 \(k=3\)

严谨地将 \(k\) 定义出来是:

\[k(l,r) = \max \left\{ t \ge 0 \;\middle|\; S[r - t \cdot \dfrac{r-l+1}2+1,\; r] \text{ 以 } \dfrac{r-l+1}2 \text{ 为周期} \right\} \]

很遗憾这个名字是我自己起的,所以可能有点格格不入。

证明方式只需要用众所周知的的 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]\) 的划分数。

略去部分容斥和数学推导,可得转移式子:

\[f_i=(\sum_{j=1}^if_{j-1})-2(\sum_{j=1}^if_{j-1}[{\rm mxrep(s[j,i])}\equiv 0\pmod 2]) \]

注意到最后的转移式子的右半部分,只有 Primitive Squares 才可能有贡献,并且可以前缀和维护。

用上述算法在线求出 Primitive Squares 即可。复杂度 \(O(n\log^2n)\)

code。(自觉强制在线后在原题的提交)

参考文献

致谢

感谢 Milmon、CarroT1212 对于题解版本的初稿进行了初步阅读。

感谢 hxhhxh 在首发前耐心帮我读一遍。

posted @ 2026-08-20 17:52  Kazemaru  阅读(2)  评论(0)    收藏  举报