Lyndon分解

我们定义一个字符串是 \(\text{Lyndon}\) 串,当且仅当这个串本身就是它的最小后缀。

首先很显然的是,若 \(S\) 是一个 \(\text{Lyndon}\) 串对于 \(∀i∈[1,|S|],S[i]\ge S[1]\)


引理1:如果 \(u\)\(v\) 都是 \(\text{Lyndon}\) 串,并且 \(u<v\),则 \(uv\) 也是 \(\text{Lyndon}\) 串。

我们设 \(u'\)\(uv\) 的后缀且 \(len(u')>len(v)\)\(v'\)\(uv\) 的后缀且 \(len(v')\le len(v)\)

由于 \(u,v\) 都是 \(\text{Lyndon}\) 串,则 \(u<u'<v<v'\),又 \(uv<u\),所以可得 \(uv\) 也是 \(\text{Lyndon}\) 串。


定义一个串 \(S\)\(\text{Lyndon}\) 分解为一个字符串序列 \(S_1,S_2,S_3...S_m\),且要满足:

  • \(∀i∈[1,m]\),满足 \(S_i\)\(\text{Lyndon}\) 串。

存在性证明

起初我们可以将 \(S\) 分为 \(|S|\)\(\text{Lyndon}\) 串,然后每次找到相邻的两个串且 \(S_i<S_{i+1}\),将它们合并成一个串。重复这一操作,最后一定满足任意的 \(S_i\ge S_{i+1}\)

唯一性证明

假设对于 \(S\) 存在两个 \(\text{Lyndon}\) 分解:

\[S=S_1S_2...S_{i-1}S_iS_{i+1}...S_{m_1} \]

\[S=S_1S_2...S_{i-1}S'_{i}S'_{i+1}...S'_{m_2} \]

我们设 \(len(S_i)>len(S'_i)\)

那么 \(S_i=S'_iS'_{i+1}S'_{i+2}...S'_{k-1}S''_k\)。其中 \(S''_k\)\(S'_k\) 的前缀。

\(\text{Lyndon}\) 串的性质得:

\[S_i<S''_k\le S'_k\le S'_i<S_i \]

\(S_i<S_i\) 矛盾,假设不成立。所以可得 \(\text{Lyndon}\) 分解唯一。


引理2:若字符串 \(v\) 和字符 \(c\) 满足 \(vc\) 是某个 \(\text{Lyndon}\) 串的前缀,则对于字符 $ d>c$ 有 \(vd\)\(\text{Lyndon}\) 串。

设这个 \(\text{Lyndon}\) 串是 \(vct\)\(v\) 的一个后缀为 \(v'\)

那么可得: \(v'ct > vct\),也就是 \(v'c \ge v\)

所以 \(v'd > v'c \ge v\)

又因为 \(c > v[1]\),所以 \(d>c>v[1]\)\(vd\)\(\text{Lyndon}\) 串。


性质1:若字符串 \(S\)\(\text{Lyndon}\) 串,那么它没有 \(\text{border}。\)

证明:假设 \(S\)\(\text{border}\),那么有一个后缀等于前缀,于是这个后缀就小于 \(S\) 了,假设不成立。

性质2:如果 \(S\)\(\text{Lyndon}\) 串,\(s=uv\)\(|u|>0,|v|>0\),满足 \(u<v\)

证明:这个就是反过来的引理1,因为 \(u<uv\)\(uv>v\) 所以 \(u<v\)

性质3:如果 \(|s|>2\)\(s\) 是一个 \(\text{Lyndon}\) 串充要条件是 \(\exists ~u,v\) 使得 \(s=uv\),且 \(|u|>0,|v|>0,u<v\) 并且 \(u,v\) 都是 \(\text{Lyndon}\) 串。


\(\text{Duval}\) 算法

\(\text{Duval}\) 可以在 \(O(n)\) 的时间内求出一个串的 \(\text{Lyndon}\) 分解。

首先我们介绍另外一个概念:如果一个字符串 \(t\) 能够分解为 \(t=ww...\bar w\) 的形式,其中 \(\bar w\) 是一个 \(\text{Lyndon}\) 串,而 \(\bar w\)\(w\) 的前缀( 可能是空串),那么称 \(t\) 是近似简单串,或者近似 \(\text{Lyndon}\) 串。一个 \(\text{Lyndon}\) 串也是近似 \(\text{Lyndon}\) 串。

\(\text{Duval}\) 算法运用了贪心的思想。算法过程中我们把串 \(S\) 分成三个部分 \(S=S_1S_2S_3\),其中 \(S_1\) 是一个 \(\text{Lyndon}\) 串,它的 \(\text{Lyndon}\) 分解已经记录; \(S_2\) 是一个近似 \(\text{Lyndon}\) 串; \(S_3\) 是未处理的部分。

整体描述一下,该算法每一次尝试将 \(S_3\) 的首字符添加到 \(S_2\) 的末尾。如果 \(S_2\) 不再是近似 \(\text{Lyndon}\) 串,那么我们就可以将 \(S_2\) 截出一部分前缀(即 \(\text{Lyndon}\) 分解)接在 \(S_1\) 末尾。

我们来更详细地解释一下算法的过程。定义一个指针 \(i\) 指向 \(S_2\) 的首字符,则 \(i\)\(1\) 遍历到 \(n\) (字符串长度)。在循环的过程中我们定义另一个指针 \(k\) 指向 \(S_3\) 的首字符,指针 \(j\) 指向 \(S_2\) 中我们当前考虑的字符(意义是 \(k\)\(S_2\) 的上一个循环节中对应的字符)。我们的目标是将 \(S[k]\) 添加到 \(S_2\) 的末尾,这就需要将 \(S[k]\)\(S[j]\) 做比较:

  1. 如果 \(S[j]=S[k]\),则将 \(S[k]\) 添加到 \(S_2\) 末尾不会影响它的近似简单性。于是我们只需要让指针 \(j,k\) 自增(移向下一位)即可。
  2. 如果 \(S[j]<S[k]\),那么 就变成了一个 \(\text{Lyndon}\) 串,于是我们将指针 \(k\) 自增,而让 \(j\) 指向 \(S_2\) 的首字符,这样 \(S_2\) 就变成了一个循环次数为 \(1\) 的新 \(\text{Lyndon}\) 串了。
  3. 如果 \(S[j]>S[k]\),则 \(S_2S[k]\) 就不是一个近似简单串了,那么我们就要把 \(S_2\) 分解出它的一个 \(\text{Lyndon}\) 子串,这个 \(\text{Lyndon}\) 子串的长度将是 \(k-j\),即它的一个循环节。然后把 \(S_2\) 变成分解完以后剩下的部分,继续循环下去(注意,这个情况下我们没有改变指针 \(j,k\)),直到循环节被截完。对于剩余部分,我们只需要将进度“回退”到剩余部分的开头即可。

代码:

#include<bits/stdc++.h>
#define pc(x) putchar(x)
using namespace std;
void write(int x)
{
    if(x<0){x=-x;putchar('-');}
    if(x>9)write(x/10);
    putchar(x%10+48);
}
int n,ans;
char s[5000005];
int main()
{
    scanf("%s",s+1);n=strlen(s+1);
    for(int i=1;i<=n;)
    {
        int j=i,k=i+1;
        while(k<=n&&s[j]<=s[k])
        {
            if(s[j]<s[k])j=i;
            else ++j;    ++k;
        }
        while(i<=j)
        {
            ans^=i+k-j-1;
            i+=k-j;
        }
    }write(ans),pc('\n');
    return 0;
}

这玩意根本不会考到好吧(

posted @ 2022-03-24 17:57  violetctl39  阅读(143)  评论(0编辑  收藏  举报