Lyndon分解学习小记

Lyndon 分解

Lyndon 串

如果一个串的字典序不大于它的所有后缀,这样的串就称为 Lyndon 串。

Lyndon 分解

将字符串 s s s 分解为 w 1 w 2 ⋯ w k w_1w_2\cdots w_k w1w2wk,若满足 w 1 , w 2 , ⋯   , w k w_1,w_2,\cdots,w_k w1,w2,,wk都是 Lyndon 串,且 w 1 ≥ w 2 ≥ ⋯ ≥ w k w_1\ge w_2\ge \cdots \ge w_k w1w2wk,则称 w 1 w 2 ⋯ w k w_1w_2\cdots w_k w1w2wk s s s 的 Lyndon 分解。

可以证明这样的分解若存在,则一定是唯一的。不妨设 w 1 w 2 ⋯ w k w_1w_2\cdots w_k w1w2wk v 1 v 2 ⋯ v s v_1v_2\cdots v_s v1v2vs 均为 s s s 的 Lyndon 分解,则存在 i i i 满足 w k = v k ( k < i ) w_k=v_k(kwk=vk(k<i) w i w_i wi v i v_i vi 的前缀。又因为 v i v_i vi 是 Lyndon 串,表明 v i v_i vi 小于 v i v_i vi 从位置 ∣ w i ∣ + 1 |w_i|+1 wi+1 开始的后缀,从而有 w i < w i + 1 w_iwi<wi+1,矛盾。

Lyndon 分解的存在性可以通过之后的算法给出。

Duval 算法

Duval 算法可以 O ( n ) O(n) O(n) 求出一个串的 Lyndon 分解。

若字符串 t t t 可以分解为 w w ⋯ w w ‾ ww\cdots w\overline{w} wwww,其中 w w w 是一个 Lyndon 串, w ‾ \overline{w} w w w w 的一个前缀,则称 t t t 是一个近似 Lyndon 串。一个 Lyndon 串也是一个近似 Lyndon 串。

算法思路

在 Duval 算法中,把 s s s 分成三个部分 s = s 1 s 2 s 3 s=s_1s_2s_3 s=s1s2s3 来维护。其中 s 1 s_1 s1 的 Lyndon 分解已经被求出, s 2 s_2 s2 是一个近似 Lyndon 串, s 3 s_3 s3 s s s 剩下的部分。

算法过程中维护三个指针。指针 i i i 指向 s 2 s_2 s2 的首字符,指针 j j j 指向 s 3 s_3 s3 的首字符,指针 k k k 指向 s 2 s_2 s2 中我们当前考虑的字符,即上一个循环节中与 j j j 对应的字符。若设 s 2 = w w ⋯ w w ‾ s_2=ww\cdots w\overline{w} s2=wwww,那么指针 j j j 指向的是最后一个 w w w 中的第 ∣ w ‾ ∣ + 1 |\overline{w}|+1 w+1 个字符。

Duval 算法的思路是每次尝试将指针 j j j 指向的字符加入到 s 2 s_2 s2 的末端。加入后,若 s 2 s_2 s2 仍然是近似 Lyndon 串,则继续处理。若 s 2 s_2 s2 变为了 Lyndon 串,则将 s 2 s_2 s2 看成是一个循环一次的 Lyndon 串继续处理。若 s 2 s_2 s2 不再是近似 Lyndon 串,则把 s 2 s_2 s2 前面的所有循环节加入到 s 1 s_1 s1 中。

算法过程

  • s [ j ] = s [ k ] s[j]=s[k] s[j]=s[k],表明加入 s [ j ] s[j] s[j] 后, s 2 s_2 s2 仍是近似 Lyndon 串。将指针 j j j 和指针 k k k 分别后移一位。
  • s [ j ] > s [ k ] s[j]>s[k] s[j]>s[k],表明加入 s [ j ] s[j] s[j] 后, s 2 s_2 s2 变为了一个 Lyndon 串。指针 j j j 移到 s 2 s_2 s2 的首字符,指针 k k k 后移一位。
  • s [ j ] < s [ k ] s[j]s[j]<s[k],表明加入 s [ j ] s[j] s[j] 后, s 2 s_2 s2 不再是一个近似 Lyndon 串。将 s 2 s_2 s2 前面的所有循环节(长度为 j − k j-k jk)删掉(即指针 i i i 每次往后跳 j − k j-k jk 位)并加入到 s s s 的 Lyndon 分解中。对后面的部分重复上述过程,即将指针 i i i 指向 w ‾ \overline{w} w 的首字符,并将指针 j , k j,k j,k 复原。

代码实现

vector<int> Duval(int n, char * str)
{
	int i = 1;
	vector<int> res;
	while (i <= n)
	{
		int k = i, j = i + 1;
		while (j <= n && str[j] >= str[k])
		{
			if (str[j] > str[k]) k = i;
			else k++;
			j++;
		}
		while (i <= k) res.push_back(i + j - k - 1), i += j - k;
	}
	return res;
}

算法分析

正确性:首先,分解出来的每个串都是 Lyndon 串。对于遇到 s [ j ] < s [ k ] s[j]s[j]<s[k] 情况时的 s 2 s_2 s2,其循环节 w w w 必然不会小于以 w ‾ s [ j ] \overline{w}s[j] ws[j] 为开头的串分解出来的第一个 Lyndon 串。因此分解出来的 Lyndon 串满足 w 1 ≥ w 2 ≥ ⋯ ≥ w k w_1\ge w_2\ge\cdots \ge w_k w1w2wk

时间复杂度:最外层循环中,指针 i i i 每次都会往后移,故循环次数不超过 n n n。对于内层循环,因为剩余串(即 w ‾ \overline{w} w)的长度必然小于分解出来的 Lyndon 串 w w w,故剩余串的长度之和不超过 n n n。因此内层的循环次数也是 O ( n ) O(n) O(n) 的。表明总的时间复杂度就是 O ( n ) O(n) O(n)

最小表示法

Lyndon 分解可以用来求一个串 s s s 的最小表示法。

具体来说,求出 s s ss ss 的 Lyndon 分解后,找到第一个跨过第 ∣ s ∣ |s| s 个位置的 Lyndon 串,通过 Lyndon 分解的性质可以得到,该串的首字符就是 s s s 的最小表示法的首字符。

代码(洛谷 P1368)

#include
using namespace std;

const int N = 600005;

int n, a[N];

int Duval(int n, int * a)
{
	int i = 1, ans;
	vector<int> res;
	while (i <= n / 2)
	{
		ans = i;
		int k = i, j = i + 1;
		while (j <= n && a[j] >= a[k])
		{
			if (a[j] > a[k]) k = i;
			else k++;
			j++;
		}
		while (i <= k) res.push_back(i + j - k - 1), i += j - k;
	}
	return ans;
}

int main()
{
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) scanf("%d", &a[i]), a[i + n] = a[i];
	int pos = Duval(n * 2, a);
	for (int i = 0; i < n; i++) printf("%d ", a[pos + i]);
	return 0;
}

hdu 6761 Minimum Index

给一个字符串 S S S,求 S S S的每个前缀的字典序最大的后缀所在的下标。
∣ S ∣ ≤ 1 0 6 , ∑ ∣ S ∣ ≤ 2 ∗ 1 0 7 |S|\le 10^6,\sum|S|\le 2*10^7 S106,S2107

S S S 进行Lyndon分解成 w 1 , ⋯   , w k w_1,\cdots,w_k w1,,wk 后,最后一个Lyndon串 w k w_k wk 就是 S S S 的最小后缀。用扩展kmp求出 w k w_k wk 的每个后缀与 w k w_k wk 的lcp,从后往前,设当前位置 p p p 的lcp为 L L L,则把 [ p , p + L ) [p,p+L) [p,p+L) 中未被标记的位置的答案 p p p。删去 w k w_k wk 后对剩下的串重复之前的操作。时间复杂度 O ( n ) O(n) O(n)

你可能感兴趣的:(学习小记,Lyndon分解,最小表示法)