Lyndon分解

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

首先很显然的是,若 S 是一个 Lyndon 串对于 i[1,|S|],S[i]S[1]


引理1:如果 uv 都是 Lyndon 串,并且 u<v,则 uv 也是 Lyndon 串。

我们设 uuv 的后缀且 len(u)>len(v)vuv 的后缀且 len(v)len(v)

由于 u,v 都是 Lyndon 串,则 u<u<v<v,又 uv<u,所以可得 uv 也是 Lyndon 串。


定义一个串 SLyndon 分解为一个字符串序列 S1,S2,S3...Sm,且要满足:

  • i[1,m],满足 SiLyndon 串。

存在性证明

起初我们可以将 S 分为 |S|Lyndon 串,然后每次找到相邻的两个串且 Si<Si+1,将它们合并成一个串。重复这一操作,最后一定满足任意的 SiSi+1

唯一性证明

假设对于 S 存在两个 Lyndon 分解:

S=S1S2...Si1SiSi+1...Sm1

S=S1S2...Si1SiSi+1...Sm2

我们设 len(Si)>len(Si)

那么 Si=SiSi+1Si+2...Sk1Sk。其中 SkSk 的前缀。

Lyndon 串的性质得:

Si<SkSkSi<Si

Si<Si 矛盾,假设不成立。所以可得 Lyndon 分解唯一。


引理2:若字符串 v 和字符 c 满足 vc 是某个 Lyndon 串的前缀,则对于字符 d>cvdLyndon 串。

设这个 Lyndon 串是 vctv 的一个后缀为 v

那么可得: vct>vct,也就是 vcv

所以 vd>vcv

又因为 c>v[1],所以 d>c>v[1]vdLyndon 串。


性质1:若字符串 SLyndon 串,那么它没有 border

证明:假设 Sborder,那么有一个后缀等于前缀,于是这个后缀就小于 S 了,假设不成立。

性质2:如果 SLyndon 串,s=uv|u|>0,|v|>0,满足 u<v

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

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


Duval 算法

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

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

Duval 算法运用了贪心的思想。算法过程中我们把串 S 分成三个部分 S=S1S2S3,其中 S1 是一个 Lyndon 串,它的 Lyndon 分解已经记录; S2 是一个近似 Lyndon 串; S3 是未处理的部分。

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

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

  1. 如果 S[j]=S[k],则将 S[k] 添加到 S2 末尾不会影响它的近似简单性。于是我们只需要让指针 j,k 自增(移向下一位)即可。
  2. 如果 S[j]<S[k],那么 就变成了一个 Lyndon 串,于是我们将指针 k 自增,而让 j 指向 S2 的首字符,这样 S2 就变成了一个循环次数为 1 的新 Lyndon 串了。
  3. 如果 S[j]>S[k],则 S2S[k] 就不是一个近似简单串了,那么我们就要把 S2 分解出它的一个 Lyndon 子串,这个 Lyndon 子串的长度将是 kj,即它的一个循环节。然后把 S2 变成分解完以后剩下的部分,继续循环下去(注意,这个情况下我们没有改变指针 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 @   violetctl39  阅读(150)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示