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);
}
原文地址:https://www.cnblogs.com/JiaZP/p/14296459.html