Lyndon 分解

Lyndon 分解学习笔记

定义

如果串 \(s\) 的所有后缀 \(t\) 均大于 \(s\),那么称 \(s\) 为 Lyndon 串(Lyndon Word)。显然,这等价于 \(s\) 为其最小循环表示。

一个串 \(s\) 的 Lyndon 分解为将 \(s\) 拆分为若干部分 \(s_1s_2...s_k\),满足 \(s_i\) 为 Lyndon 串,且 \(s_1 \ge s_2 \ge s_3 \ge ... \ge s_k\)

性质

  • \(u,v\) 为 Lyndon 串,且 \(u<v\),则 \(uv\) 为 Lyndon 串。

  • 一个串的 Lyndon 分解有且仅有一种方案。(当 \(s_i < s_{i+1}\) 时合并两串得到新 Lyndon 串,一直这么做可得到一种方案)(如果存在多种方案,不妨设 \(s_i' = s_i + t\),其中 \(s_i\) 之前部分两种方案一致。那么 \(t > s_i'\),那么 \(s_{i+1} > s_i\)

  • 如果 \(sc\) 可以作为 Lyndon 串的前缀,其中 \(|c| = 1\),那么对于一个比 \(c\) 大的字符 \(d\),有 \(sd\) 一定是 Lyndon 串。

  • 如果 \(s_1\) 为 Lyndon 串且 \(s_1 > s_2\),那么 \(s_1 > s_2 + s_2\)

  • Lyndon 串不含 border

构造方法

因为 Lyndon 分解的限制只在相邻两个 Lyndon 串上,我们只用管最近的 Lyndon 串即可。

将串 \(s\) 分为 已经确定好的 Lyndon 串 \(A\),未确定的(可能出现循环的)串 \(B\),以及尚未考虑的串 \(C\)

假设 \(B\) 的周期为 \(l\),每个循环节均为一个 Lyndon 串。如果 \(C\) 的第一个字符 \(s_k\)\(s_{k-l}\) 大,那么可以将 \(B\)\(s_k\) 合并成一个 Lyndon 串,周期为 \(|B|+ 1\)。如果 \(C\) 的第一个字符 \(s_k < s_{k-l}\),那么 \(B\) 中前面所有满的循环节都可以拆出来成为 Lyndon 串了,因为它们不可能继续和 \(s_k\) 拼一块。如果 \(s_k = s_{k-l}\),那么我们只好将 \(s_k\) 加入 \(B\) 继续观察。

均摊复杂度为 \(O(n)\)

for (int i = 1; i <= n; ) {//i:A后面那个位置
	int j = i, k = j + 1;//j:k-l
	while (k <= n && s[k] >= s[j]) {
		if (s[k] == s[j])	++j;
		else	j = i;
		++k;
	}
	while (i <= j) {
		Push(i, i + (k-j) - 1);//i...i+k-j-1 为一个 Lyndon 串
		i += (k - j);
	}
}

最小表示

如何利用 Lyndon 分解求一个串 \(s\) 的最小表示?

\(ss\) 进行 Lyndon 分解,其中开头在 \(1...n\) 的 Lyndon Word 的开头 \(i\) 记为最小表示的开头

因为后面的肯定不行(其实会发现后面好像都成了一大块 Lyndon Word,除了循环串),前面的 Lyndon 串比这个串大的也肯定不行,前面的和它相等的也都可以。

不过如果前面出现和它相等的 Lyndon Word 的话一定是出现了循环串(如 abab -> abababab)否则后面不可能再分解出 Lyndon 串了,因为分解出的 Lyndon 串必须小于等于前面和它相同的部分的 Lyndon Word,这只有循环串才能实现。

for (int i= 1; i <= n; ++i)	s[i + n] = s[i];
int ans = 0;
for (int i = 1; i <= n; ) {
	int j = i, k = i + 1; ans = i;
	while (k <= n + n && s[k] >= s[j]) {
		if (s[k] == s[j])	++j;
		else	j = i;
		++k;
	}
	while (i <= j) i += (k - j);
}
posted @ 2021-01-19 10:07  JiaZP  阅读(237)  评论(0编辑  收藏  举报