Lyndon 分解

Lyndon 分解

  • 首先有性质,任意字符串存在唯一 \(Lyndon\) 分解。
  • Lyndon 串,须满足,Lyndon 串本身,是它的最小后缀。
  • Lyndon 分解,就是把字符串划分成若干 Lyndon 串 \(w_1,w_2,\cdots,w_n\),需满足 \(w_1\ge w_2\cdots\ge w_n\)
  • 我们考虑给出一种策略。
  • 和大部分字符串算法一样,我们考虑一次加入 \(s_k\) 这个字符,看影响。
  • 嗯,凭经验,我们需要有一个变量 \(j\),表示 \([1,j]\) 已经 Lyndon 分解完了。
  • 显然即使 串 \([j+1,k]\) 是合法的 Lyndon 串,也不能就直接划分。
  • 考虑反例充实算法。可能破坏 \(w_p\ge w_{p+1}\) 的性质,那我们考虑若一个字符大于 \(s_{j+1}\) 的都划分在一个 Lyndon 串中。
  • 可是等于呢?设这个位置为 \(t\) 等于之后若是后面再加入的部分不和前面循环就不好了,循环指 \([j+1,t-1]\) 的前缀等于 \([t,i]\)
  • 考虑大于上次循环位置的情况,这不会破环 Lyndon 串本身的性质,所以不用管,由于循环改变直接,设 \([j+1,i]\) 为新的循环即可。
  • 若是小于上次循环的位置,我们一定不能将 \(s_i\) 加入,要不必然会破坏 Lyndon 串的性质。于是我们把前面的所有循环依次剥离成为小的 Lyndon 串,然后重新匹配剩余部分。
  • 额可是我们为什么不直接遇到 \(s_i=s_{j+1}\) 直接分段呢?还要记录循环,那时因为直接分段就无法判断 大于上次循环位置的情况,而这种情况恰恰要求必须和前面和二为一
  • 接下来就是代码实现的细节,和时间复杂度,时间复杂度线性带一些小常数而以(没学证明,就逃了)
  • 我们维护 \(i,j,k\) 分别表示 \([1,i]\) 已经划分完,新加入 \(s_k\)\(j\)\(k\) 应该所对应的循环节的位置。
#include <cstring>
#include <iostream>
using namespace std;
const int N = 5000100;
char s[N];
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr); cout.tie(nullptr);
    cin >> s + 1;
    int n = strlen(s + 1), i = 1;
    int ans = 0;
    while (i <= n)
    {
        int j = i + 1, k = i;
        while (j <= n && s[j] >= s[k])
        {
            if (s[j] == s[k])k++;
            else k = i; j++;
        }
        while (i <= k)i += j - k, ans ^= i - 1;//[i-j+k,i-1]
    }
    cout << ans << '\n';
    return 0;
}
posted @ 2025-04-23 08:01  LUHCUH  阅读(66)  评论(0)    收藏  举报