本文以文字说明为主,记号较少,可能存在一些非正式表述,请谅解。
前置知识:字符串哈希或后缀数组,脑子。
记 sisi 为字符串 ss 的第 ii 个字符(11 下标),默认字符串的长度是 nn。
记 sl,rsl,r 为从 ll 到 rr 的字符组成的子串。
称一个字符串的 ii 后缀为从第 ii 个字符开始的后缀,ii 前缀则是到第 ii 个字符终止的前缀。
如果没有讨论越界问题,默认 s0=sn+1=−infs0=sn+1=−inf,即小于任意字符。
首先,什么是 runs?
一个字符串 ss 中的一个 runs 是一个循环子串,例如 aabcabcabb
中 abcabcab
这个子串就是一个 runs。
一个 runs 必然存在最小周期,记最小周期长度为 pp,对应子串是 sl,rsl,r,则一个 runs 还要满足:
- 长度至少为 2p2p,即周期至少出现两遍;
- 不能向两侧扩展,即 sr+1≠sr+1−psr+1=sr+1−p;对于左侧同理。
所以,对于串 aabcabcabb
而言,子串 abcab
和 abcabca
都不能算作 runs。
接下来讨论如何去求所有 runs。
如果现在已经找到了某个 runs 的某个最小周期 si,jsi,j,那么可以通过对 ii 后缀和 j+1j+1 后缀求一次最长公共前缀来向右扩展,以及对 i−1i−1 前缀和 jj 前缀求最长公共后缀来向左扩展,最后检查是否满足 runs 的条件就能找到这个 runs。
任意一个在 runs 范围中且长度为 pp 的子串其实都能被称为 "周期",下文直接称其为周期。
记上述操作为一次检查。要想找出所有 runs,只需要检查所有可能的周期即可。
任何一个 runs 都有 pp 种周期,他们互为循环同构的串,仍然假设这个 runs 是 sl,rsl,r。
从其中最小的串入手,假设这是 si,jsi,j(因为长度至少是 2p2p 所以这样的串必然存在)。
这玩意其实可以叫最小表示法,一般的求法是重复原串两遍,取 1∼n1∼n 后缀中最小的后缀再截取 nn 前缀就行。
但是由于这是 runs,可以证明这还是一个 Lyndon Word,即所有后缀都比原串大,以下是简要证明:
不妨假设我们找到的串是 rr 有一个后缀 ss 比原串小。因为任何循环同构都比 rr 大,可以推出 ss 是 rr 的前缀,又因为 rr 是最短周期,所以 rr 必然没有大于等于原串长度一半的 border,则令 r=s+t+sr=s+t+s
由 rr 是所有循环同构中最小的,可得 s+t+s≤s+s+ts+t+s≤s+s+t,s+t+s≤t+s+ss+t+s≤t+s+s 从而推得矛盾。
所以对于任意 i+1≤k≤ji+1≤k≤j,kk 后缀要大于 ii 后缀。
那么 j+1j+1 后缀是 ii 后面第一个可能比 ii 后缀小的后缀。
同时可以发现 j+1j+1 后缀与 ii 后缀的大小关系取决于 sr+1sr+1 与 sr+1−psr+1−p 的大小关系(就是之前取最长公共前缀的两个后缀)。
所以,通过找到所有 ii 后缀后面第一个比他小的后缀,并且检查对应区间,可以找出所有 sr+1<sr+1−psr+1<sr+1−p 的 runs。
对于 sr+1<sr+1−psr+1<sr+1−p 的 runs,从最大的周期(即反转字符大小关系下的最小,注意此时空字符应当大于所有字符)入手即可。即找到所有 ii 后缀后面第一个比他大的后缀,就可以找出所有这一类 runs。
不存在 sr+1=sr+1−psr+1=sr+1−p 的情况,所以,通过检查至多 2n2n 次,可以找出所有 runs。
这也就是 runs 数量小于等于 2n2n 的证明。
最后,如果这个 runs 包含 kk 个完整周期,那么最小/大周期也会出现至少 k−1k−1 次,所以记得去重。
讨论一些实现细节。
第一种实现是使用后缀排序。需要写正串和反串两遍 SA。通过写 SA-IS 加上四毛子可以做到线性求 runs。
第二种是直接字符串哈希,使用二分哈希/倍增哈希 O(logn)O(logn) 求最长公共前后缀以及后缀比较。使用单调栈就只需要 O(n)O(n) 次比较,总复杂度也是 O(nlogn)O(nlogn)。
代码就不放了,哈希应该不至于不会写吧,记得去重。