如果一个串的字典序不大于它的所有后缀,这样的串就称为 Lyndon 串。
将字符串 s s s 分解为 w 1 w 2 ⋯ w k w_1w_2\cdots w_k w1w2⋯wk,若满足 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 w1≥w2≥⋯≥wk,则称 w 1 w 2 ⋯ w k w_1w_2\cdots w_k w1w2⋯wk 是 s s s 的 Lyndon 分解。
可以证明这样的分解若存在,则一定是唯一的。不妨设 w 1 w 2 ⋯ w k w_1w_2\cdots w_k w1w2⋯wk 和 v 1 v 2 ⋯ v s v_1v_2\cdots v_s v1v2⋯vs 均为 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_i
Lyndon 分解的存在性可以通过之后的算法给出。
Duval 算法可以 O ( n ) O(n) O(n) 求出一个串的 Lyndon 分解。
若字符串 t t t 可以分解为 w w ⋯ w w ‾ ww\cdots w\overline{w} ww⋯ww,其中 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=ww⋯ww,那么指针 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 中。
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 w1≥w2≥⋯≥wk。
时间复杂度:最外层循环中,指针 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 的最小表示法的首字符。
#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;
}
给一个字符串 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 ∣S∣≤106,∑∣S∣≤2∗107
把 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)。