【模板】Lyndon 分解

题意

定义 $\text{Lyndon}$ 串为:其本身是最小后缀的串。

再定义 $\text{Lyndon}$ 分解为:将一个串分解为一些 $\text{Lyndon}$ 串,且这些串的字典序是一个单调不升的关系。

求一个串的 $\text{Lyndon}$ 分解的方案。

思路

首先,我们能确定:$\text{Lyndon}$ 分解一定存在且唯一。

用构造法证明:首先,找出最小后缀,作为一个 $\text{Lyndon}$ 串。然后,在剩余字符串里一直重复此过程。一直到剩余的字符串为空为止。

首先,上述证明有解是显而易见的;其次,每次我们若不按照上述方法取的话,分两种情况:

  • 取的一段比最小后缀长

    显然这个字符串不是 $\text{Lyndon}$ 串。

  • 取的后缀比最小后缀短

    不满足 $\text{Lyndon}$ 串单调不升的定义。假设最小后缀从第 $i$ 个字符开始,那么 $i$ 位置必然作为一个 $\text{Lyndon}$ 串的开头(不然 $i$ 位置所处的那个串就不是 $\text{Lyndon}$ 串)。再由于当前是一段后缀,位置必定在剩下的所有分解的串之后,显然 $i$ 位置所处的那个 $\text{Lyndon}$ 串与当前后缀不满足不升序的关系。

故而只存在唯一解。

那我们该怎么求呢?

方法 $1$:后缀数组 $\text{SA}$

我们考虑按照上述构造法中规中矩地求。会发现求最小后缀一事可以对后缀进行排序,然后把所有的后缀丢进一个堆里,堆顶为拥有最小字典序的后缀。若我们发现堆顶的后缀是一个合法的后缀(假设当前的剩余字符串为 $1$ 到 $x$,然后堆顶是 $y$ 开头的后缀,合法当且仅当 $1 \leq y \leq x$),那么最小后缀就是它。否则删除堆顶元素,因为之后它也不可能合法了。

注:上述合法的定义表达的意思是堆顶的后缀与当前剩余的字符串有交,即产生的 $\text{Lyndon}$ 串不为空。

用倍增法求解,时间复杂度 $O(n \log n)$;若是 $\text{DC3}$ 算法,时间复杂度 $O(n)$。

方法 $2$:$\text{Duval}$ 算法

注意,本方法与上述构造法无关。

考虑正着求。我们将把字符串 $s$ 分为 $3$ 个部分:

  1. $s1$ 表示前面已经分解完的 $\text{Lyndon}$ 串。
  2. $s2$ 表示当前正在处理的 $\text{Lyndon}$ 串。
  3. $s3$ 表示未分解、未在处理的剩下的串。

有 $s=s1+s2+s3$($+$ 表示字符串拼接)。

$\text{Duval}$ 算法的思想就是每次保证 $s2$ 是 $\text{Lyndon}$ 串的情况下,且让 $s2$ 的元素越多越好。具体来说,是要维护一个周期,下面会有详细的讲解。

  • 先给出以下定理:

    若 $a,b$ 都是一个 $\text{Lyndon}$ 串,且 $a<b$,那 $a+b$ 必定也是 $\text{Lyndon}$ 串。

    证明:考虑 $b$ 既然是 $\text{Lyndon}$ 串,那最小后缀必定为其本身。要想 $a+b$ 不是 $\text{Lyndon}$ 串,必定有 $a>b$,与条件不符。

于是我们设当前 $s2$ 的开头为位置 $i$,要考虑加入的字符为 $k$,当前 $k$ 与周期中的 $j$ 位置作比较,有以下三种情况:

  1. $s_k=s_j$

    无事发生。$j$ 自增 $1$,$k$ 也自增 $1$,继续对下一个字符的比较。

  2. $s_k>s_j$

    即当前的字符加入依旧是一个 $\text{Lyndon}$ 串(根据上述定理),由于字符的不一样,前面的所有周期加这个 $s_k$ 都变成同一个 $\text{Lyndon}$ 串。

    由于当前整个变成了一个新周期,继续从头开始比较,所以有:$j$ 赋值为 $i$,$k$ 自增 $1$。

  3. $s_k<s_j$

    显然不再满足一个 $\text{Lyndon}$ 串的性质。假设 $t$ 为当前 $s2$ 中的一个周期,$t'$ 为 $t$ 的前缀,有 $s2=t+t+...+t+t'$,所以把之前的每个周期 $t$ 分解为一个 $\text{Lyndon}$ 串。对于 $t'$,继续和后面的字符做 $\text{Lyndon}$ 分解。

    若论更为具体的处理,我们有:

    • 周期长度为 $k-j$。
    • 从 $i$ 开始,一直加上这个周期,边加边统计答案 $i-1$。重复此操作直到 $i + (k-j) \leq k$(即 $k$ 前的最后一个周期),移项得 $i \leq j$。

时间复杂度 $O(n)$。假设每次寻找到的周期长度为 $len$,那么我们每次会处理掉那些完整的周期 $t$,最后的 $t'$ 留到下次去处理。发现 $t'$ 的长度必定不大于周期的长度。所以每删除一段,最多产生双倍的线性时间,且删除了就放入了 $s1$ 不会再恢复。故而为 $O(n)$。

#include <bits/stdc++.h>
#define L(i, a, b) for(int i = (a); i <= (b); i++)
#define R(i, a, b) for(int i = (a); i >= (b); i--)
using namespace std;
const int N = 5e6 + 10;
int n, i = 1, j, k, ans;
char s[N];
int main(){
    scanf("%s", s + 1), n = strlen(s + 1);
    while(i <= n){
        for(j = i, k = i + 1; k <= n && s[k] >= s[j]; k++)
            j = (s[k] == s[j]? j + 1 : i);
        for(; i <= j; i += k - j, ans ^= i - 1);
    }
    printf("%d\n", ans);
    return 0;
}
posted @ 2023-05-21 08:08  徐子洋  阅读(16)  评论(0)    收藏  举报  来源