做CF594E涉及的两个知识点。以下字符串采用Python记法。
Lyndon分解
定义 $S$ 是Lyndon串,当且仅当对于任意有意义的正整数 $i$ 有 $S
定义 $S$ 的Lyndon分解是一个Lyndon串的序列 $s_1, s_2, \ldots, s_n$, 使得 $S=s_1s_2 \cdots s_n$ 并且 $s_1 \ge s_2 \ge \cdots \ge s_n$.
Lyndon分解存在且唯一。
不难发现,Lyndon分解可以这么得到:对于 $S$, 取最小的后缀 $S[i:]$, $S$ 的Lyndon分解,是在 $S[:i]$ 的Lyndon分解的最后加上一项 $S[i:]$ 得到的序列。
于是,Lyndon分解可以用后缀数组求,但是这样太复杂了。
Lyndon分解的Duval算法:
设原字符串为 $S$. 逐个加入字符,设已能确定 $S[:i]$ 的Lyndon分解 $s_1, s_2, \ldots, s_n$ 为 $S$ 的Lyndon分解的前缀,再尽可能地扩充字符串 $S[i:k]$, 使得 $S[i:k]$ 具有Lyndon周期 $t$, 也就是说,$S[i:k]=(m \times t)u$, 其中 $m$ 是正整数,$t$ 是Lyndon串,$u$ 是 $t$ 的(可以为空的)真前缀。考虑加入字符 $S[k]$, 若 $k=|S|$ 则令 $S[k]=-\infty$.
- 若 $S[k]
- 若 $S[k]=S[k-|t|]$, 则 $t$ 也是 $S[i:k+1]$ 的Lyndon周期。
- 若 $S[k]>S[k-|t|]$, 则 $S[i:k+1]$ 的Lyndon周期是其本身。
实现上我们只需要维护 $i, k$ 以及 $j=k-|t|$. 代码链接
扩充 $S[i:k]$ 的过程中 $i+k\le2|S|$ 且随扩充总轮数递增,因此该算法时间 $O(|S|)$, 除去输入输出只需要 $O(1)$ 额外空间,是一个非常简短而高效的算法。
最小循环表示
最小循环表示,也就是对于字符串 $S$, 求出最小的 $T_i=S[i:]S[:i]$.
我们维护两个决策 $i, j$, 满足 $i 初始时,设 $i=0, j=1$. 我们通过枚举比较求出 $T_i$ 和 $T_j$ 的最长公共前缀 $k$. 当 $j \ge |S|$ 时,我们发现 $[0, i)$ 和 $(i, |S|)$ 中的整数都不可能为最小决策了,所以 $T_i$ 是唯一的最优解;否则重复此过程。 $i+j+k \le 3|S|$ 随枚举总次数递增,因此该算法时间 $O(|S|)$, 除去输入输出只需要 $O(1)$ 额外空间,是一个非常简短而高效的算法。 代码:$n$($n \le 3\times10^5$)项整数序列的最小循环表示。 不过这份代码由于在OJ上刷常数榜,不仅加入了输入输出优化,还破环为链以节省取模的时间,空间略大。
1 #include