【模板】Lyndon 分解
题目链接:luogu P6114
题目大意
求 Lyndon 分解得到的每个区间的右端点的异或和。
思路
有关证明可以查阅 command_block 大神的博客,从那里学的/fad。
才发现,后缀自动机只是字符串难的开始。。。
(好吧其实 Border 已经不会了)
定义
Lyndon Word
如果一个字符串 s s s 满足它的所有后缀 t t t(不包括 s s s)都有 s < t s<t s<t(字典序),那 s s s 就是关于 < < < 的一个 Lyndon Word。
你也可以在字符集 ∑ \sum ∑ 上定义相反的全序关系 < 0 , < 1 <_0,<_1 <0,<1(比如正反字典序),这样就是关于 < 0 / 1 <_{0/1} <0/1 的了。
Lyndon 分解
把字符串 s s s 分解成 s 1 , s 2 , . . . , s m s_1,s_2,...,s_m s1,s2,...,sm 都是 Lyndon Word,而且 s 1 ⩾ s 2 ⩾ . . . ⩾ s m s_1\geqslant s_2\geqslant ...\geqslant s_m s1⩾s2⩾...⩾sm。
性质
(很多都不会证或者懒得证,感性理解)
- Lyndon Word 不会有 Border
显然,因为这样就是后缀,而且又是前缀,那字典序肯定是小于整个串的。
- 如果一个 Lyndon Word s = a b s=ab s=ab 那 a < b a<b a<b。
不然的话就不满足定义了。(
a
b
<
b
,
a
<
a
b
ab<b,a<ab
ab<b,a<ab)
然后因此也有
a
a
a 不是
b
b
b 的前缀的话
a
c
<
b
d
ac<bd
ac<bd 跟
a
<
b
a<b
a<b 是充要条件。
- 对于 Lyndon Word a a a 如果有 b < a b<a b<a,有 b a < a ba<a ba<a
挺显然的,就分 b b b 是否是 a a a 的子串来讨论即可。
- 如果有一个字符串 s s s 和一个字符 x x x 使得 s x sx sx 是某个 Lyndon Word 的前缀,那对于字典序比 x x x 小的字符 y y y(即 y < x y<x y<x), s y sy sy 也一定是 Lyndon Word。
设那个 Lyndon Word 是
s
x
t
sxt
sxt,然后你对于
s
s
s 的每个位置考虑加上
x
x
x 是不是
s
s
s 前缀。
如果是,那改成
y
y
y 更小(而且互不为前缀),那我们可以用
2
2
2 里面的因此的结论得到
s
y
<
sy<
sy< 改成
y
y
y 得到的串。
如果不是,考虑直接在这个部分接上
t
t
t,也可以通过那个东西得到接上
y
y
y 是
>
s
y
>sy
>sy 的。
然后两个结合起来就得证了。
(不难看出我其实看不懂这个部分,硬胡了属于是)
- s s s 是 Lyndon Word 当且仅当它可以被表示成 a b ab ab,而且 a , b a,b a,b 都是 Lyndon Word,且 a < b a<b a<b。
首先
b
b
b 那边肯定可以,然后看
a
a
a 那边的。(肯定可以是你传递一下
a
<
b
,
b
<
a<b,b<
a<b,b< 你那个串,就可以了)
发现因为没有 Border,所以一定会在
b
b
b 前面比较完,所以也可以。
这是一个十分重要的信息。
- Lyndon 拆分唯一且存在
首先是唯一,这个其实挺显然的,你考虑不同的位置,然后看大的那个会包含另一种若干个全部和一个一部分。
那这个这个一部分在大的那个里面是后缀,就
>
>
> 了,然后你可以推回去
>
>
> 大的那些,就矛盾了。
注意到一定会有后面的那个部分,不然要么就一样要么就应该是另一个串的大了。
存在这个就要利用我们的
5
5
5 性质了。
不难看出一个使用于任何一个串构造的方法:
一开始都是一个字符(肯定可以),然后你每次找相邻的前面的比后面的字典序小的合并起来,就可以了。
Duval算法
为什么要用这个呢?
因为你会发现我们上面构造的方法很麻烦。
然后如果你每次选一个最小的后缀的话就要后缀数组排序,不仅是
O
(
n
log
n
)
O(n\log n)
O(nlogn) 也很麻烦。
于是就有了
O
(
n
)
O(n)
O(n) 的简介的 Duval 算法!
你会发现刚刚我们想了后缀,那我们能不能找前缀呢!
其实是可以的,就是 Duval 算法。
然后又一个定理:
设
s
=
u
k
u
′
x
s=u^ku'x
s=uku′x(其中
u
′
u'
u′ 是
u
u
u 的前缀,可以是空的,
x
x
x 是一个字符,而且不能接在
u
′
u'
u′,也就是不是这个循环的)
如果
x
>
x>
x>
u
′
u'
u′ 下一个应该要是的字符,那根据上面的性质
5
5
5 我们可以知道
s
s
s 就是一个 Lyndon Word。
如果
x
>
u
′
x>u'
x>u′ 下一个应该要是的字符,那最长前缀 Lyndon Word 是
u
u
u:
如果选
u
k
u
′
u^ku'
uku′ 的其中一个前缀(大于
u
u
u 的),那会有 border 不满足条件。
那唯一一个可能既是
u
k
u
′
x
u^ku'x
uku′x 了。那我们会发现
u
′
x
<
u
<
u
k
u
′
x
u'x<u<u^ku'x
u′x<u<uku′x 所以不行。
那 Duval 就是维护这个
s
s
s,假设当前已经划分好
1
∼
p
−
1
1\sim p-1
1∼p−1 的,那就是要解决
p
∼
n
p\sim n
p∼n,然后当前在
i
i
i,表示乘了
u
c
u
′
u^cu'
ucu′ 的形式,我们需要记录的就是
∣
u
∣
,
c
,
∣
u
′
∣
|u|,c,|u'|
∣u∣,c,∣u′∣。
加入一个
s
i
+
1
s_{i+1}
si+1:
如果是要求的字符,
u
′
u'
u′ 扩展,并查看是否会贡献给
c
c
c。
如果大于,我们直接把
u
=
i
−
p
+
1
u=i-p+1
u=i−p+1(不要直接统计,因为可能会这个循环)。
如果小于,我们就把
c
c
c 个
u
u
u 贡献上,然后你考虑
u
′
u'
u′ 怎么处理,会发现我们不能确定里面会不会循环,所以我们要把
i
i
i 退回到
u
′
u'
u′ 前的位置(也就是
p
p
p,因为你贡献
u
u
u 的时候就加了
p
p
p)
然后考虑分析一下复杂度,每个 u ′ u' u′ 的长度不会超过 u u u,而且 u u u 每个点只会进入一次,所以最多退 O ( n ) O(n) O(n) 次,总复杂度是 O ( n ) O(n) O(n) 的。
然后要注意的是做完之后我们要处理掉剩下的 u , c , u ′ u,c,u' u,c,u′,有一个很好的方法就是 i i i 从 1 1 1 做到 n n n,然后我们让 s n + 1 = − ∞ s_{n+1}=-\infty sn+1=−∞,那每次必然会小于,然后就可以不断的处理 u ′ u' u′ 消完它了。
EX
好像说可以用 Duval 算法求字符串每个前缀的最小 / 最大后缀。
还可以求
s
s
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;
}