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

【模板】Lyndon 分解

题目链接:luogu P6114

题目大意

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

思路

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

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

定义

Lyndon Word

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

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

Lyndon 分解

把字符串 \(s\) 分解成 \(s_1,s_2,...,s_m\) 都是 Lyndon Word,而且 \(s_1\geqslant s_2\geqslant ...\geqslant s_m\)

性质

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

  1. Lyndon Word 不会有 Border

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

  1. 如果一个 Lyndon Word \(s=ab\)\(a<b\)

不然的话就不满足定义了。(\(ab<b,a<ab\)
然后因此也有 \(a\) 不是 \(b\) 的前缀的话 \(ac<bd\)\(a<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(n\log n)\) 也很麻烦。
于是就有了 \(O(n)\) 的简介的 Duval 算法!

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

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

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

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

然后要注意的是做完之后我们要处理掉剩下的 \(u,c,u'\),有一个很好的方法就是 \(i\)\(1\) 做到 \(n\),然后我们让 \(s_{n+1}=-\infty\),那每次必然会小于,然后就可以不断的处理 \(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;
}
posted @ 2022-07-22 19:28  あおいSakura  阅读(59)  评论(0编辑  收藏  举报