【luogu P6114】【模板】Lyndon 分解(Duval算法)

【模板】Lyndon 分解

题目链接:luogu P6114

题目大意

求 Lyndon 分解得到的每个区间的右端点的异或和。

思路

有关证明可以查阅 command_block 大神的博客,从那里学的/fad。

才发现,后缀自动机只是字符串难的开始。。。
(好吧其实 Border 已经不会了)

定义

Lyndon Word

如果一个字符串 s 满足它的所有后缀 t(不包括 s)都有 s<t(字典序),那 s 就是关于 < 的一个 Lyndon Word。

你也可以在字符集 上定义相反的全序关系 <0,<1(比如正反字典序),这样就是关于 <0/1 的了。

Lyndon 分解

把字符串 s 分解成 s1,s2,...,sm 都是 Lyndon Word,而且 s1s2...sm

性质

(很多都不会证或者懒得证,感性理解)

  1. Lyndon Word 不会有 Border

显然,因为这样就是后缀,而且又是前缀,那字典序肯定是小于整个串的。

  1. 如果一个 Lyndon Word s=aba<b

不然的话就不满足定义了。(ab<b,a<ab
然后因此也有 a 不是 b 的前缀的话 ac<bda<b 是充要条件。

  1. 对于 Lyndon Word a 如果有 b<a,有 ba<a

挺显然的,就分 b 是否是 a 的子串来讨论即可。

  1. 如果有一个字符串 s 和一个字符 x 使得 sx 是某个 Lyndon Word 的前缀,那对于字典序比 x 小的字符 y(即 y<x),sy 也一定是 Lyndon Word。

设那个 Lyndon Word 是 sxt,然后你对于 s 的每个位置考虑加上 x 是不是 s 前缀。
如果是,那改成 y 更小(而且互不为前缀),那我们可以用 2 里面的因此的结论得到 sy< 改成 y 得到的串。
如果不是,考虑直接在这个部分接上 t,也可以通过那个东西得到接上 y>sy 的。
然后两个结合起来就得证了。
(不难看出我其实看不懂这个部分,硬胡了属于是)

  1. s 是 Lyndon Word 当且仅当它可以被表示成 ab,而且 a,b 都是 Lyndon Word,且 a<b

首先 b 那边肯定可以,然后看 a 那边的。(肯定可以是你传递一下 a<b,b< 你那个串,就可以了)
发现因为没有 Border,所以一定会在 b 前面比较完,所以也可以。
这是一个十分重要的信息。

  1. Lyndon 拆分唯一且存在

首先是唯一,这个其实挺显然的,你考虑不同的位置,然后看大的那个会包含另一种若干个全部和一个一部分。
那这个这个一部分在大的那个里面是后缀,就 > 了,然后你可以推回去 > 大的那些,就矛盾了。
注意到一定会有后面的那个部分,不然要么就一样要么就应该是另一个串的大了。

存在这个就要利用我们的 5 性质了。
不难看出一个使用于任何一个串构造的方法:
一开始都是一个字符(肯定可以),然后你每次找相邻的前面的比后面的字典序小的合并起来,就可以了。

Duval算法

为什么要用这个呢?
因为你会发现我们上面构造的方法很麻烦。
然后如果你每次选一个最小的后缀的话就要后缀数组排序,不仅是 O(nlogn) 也很麻烦。
于是就有了 O(n) 的简介的 Duval 算法!

你会发现刚刚我们想了后缀,那我们能不能找前缀呢!
其实是可以的,就是 Duval 算法。

然后又一个定理:
s=ukux(其中 uu 的前缀,可以是空的,x 是一个字符,而且不能接在 u,也就是不是这个循环的)
如果 x> u 下一个应该要是的字符,那根据上面的性质 5 我们可以知道 s 就是一个 Lyndon Word。
如果 x>u 下一个应该要是的字符,那最长前缀 Lyndon Word 是 u
如果选 uku 的其中一个前缀(大于 u 的),那会有 border 不满足条件。
那唯一一个可能既是 ukux 了。那我们会发现 ux<u<ukux 所以不行。

那 Duval 就是维护这个 s,假设当前已经划分好 1p1 的,那就是要解决 pn,然后当前在 i,表示乘了 ucu 的形式,我们需要记录的就是 |u|,c,|u|
加入一个 si+1
如果是要求的字符,u 扩展,并查看是否会贡献给 c
如果大于,我们直接把 u=ip+1(不要直接统计,因为可能会这个循环)。
如果小于,我们就把 cu 贡献上,然后你考虑 u 怎么处理,会发现我们不能确定里面会不会循环,所以我们要把 i 退回到 u 前的位置(也就是 p,因为你贡献 u 的时候就加了 p

然后考虑分析一下复杂度,每个 u 的长度不会超过 u,而且 u 每个点只会进入一次,所以最多退 O(n) 次,总复杂度是 O(n) 的。

然后要注意的是做完之后我们要处理掉剩下的 u,c,u,有一个很好的方法就是 i1 做到 n,然后我们让 sn+1=,那每次必然会小于,然后就可以不断的处理 u 消完它了。

EX

好像说可以用 Duval 算法求字符串每个前缀的最小 / 最大后缀。
还可以求 s 的最小表示,就是复制一遍弄个 Lyndon 分解,然后找起始位置在第一个串的最小 Lyndon Word 即可。这个用定义就可以证明。

代码

#include<cstdio> #include<cstring> using namespace std; const int N = 5e6 + 100; int n, ans, p, u, c, u_; char s[N]; int main() { scanf("%s", s + 1); n = strlen(s + 1);//s[n+1]=-inf u = 1; c = 1; for (int i = 1; i <= n; i++) { if (s[i + 1] == s[i - u + 1]) { u_++; if (u_ == u) {c++; u_ = 0;} } else if (s[i + 1] > s[i - u + 1]) { u = i - p + 1; c = 1; u_ = 0; } else { while (c--) {p += u; ans ^= p;} u = 1; c = 1; u_ = 0; i = p;//u'的部分重新看过 } } printf("%d", ans); return 0; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/luogu_P6114.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(81)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示