wcz在2022年集训队论文《浅谈与Lyndon理论有关的字符串组合问题》中做过详细介绍,由于笔者太菜,这里只做简单介绍,并且不做证明。
Lyndon 分解
Lyndon串:对于字符串\(s\),如果\(s\)的字典序严格小于\(s\)的所有后缀的字典序,我们称\(s\)是Lyndon串。
Lyndon分解:串\(s\)的Lyndon分解记为\(s = w^1w^2 \cdots w^k\),其中所有\(w^i\)为简单串,并且他们的字典序按照非严格单减排序,即$w^1 \geq w^2 \geq \cdots \geq w^k $。可以发现,这样的分解存在且唯一。
Duval 算法
显然我们能用\(SA\)在\(O(nlogn)\)复杂度求出来,这里介绍一种线性方法\(Duval\)算法。
我们发现Lyndon串一定形如$ww \cdots \overline w \(,其中\)\overline w \(是\)w$的前缀。
我们维护三个指针\(i,j,k\),其中\(i\)表示当前处理的串的起始位置,\(j\)是上一个循环节的位置,\(k\)是新处理字符的位置。
有三种情况:
- 当\(s[j] = s[k]\)时,依旧是循环出现的串,\(j,k\)后移。
- 当\(s[j] < s[k]\)时,说明\(s[i,k]\)是一个新的Lyndon串,我们将其加入答案,并且将\(j\)更新为\(i\),\(i\)更新为\(k\)。
- 当\(s[j] > s[k]\)时,这就成为一个Lyndon串。\(i += j - k\)
void duval(char* s, int n) {
for (int i = 1, j, k; i <= n;) {
for (j = i, k = i + 1; k <= n && s[k] >= s[j]; ++k)
if (s[k] > s[j])j = i;
else ++j;
while (i <= j) {
i += k - j;
gap.push_back(i - 1);
}
}
}
一些概念 & 性质
Lyndon Word
即Lyndon串
\(CFL(s)\)
即Lyndon分解
有效后缀
加上任意字符都是可能最小的后缀即位有效后缀
有效后缀一定形如$w_i^{c_i} \cdots w_k^{c_k} $
\(Significant /\ Suffixes /\ SS(w)\)
有效后缀族构成的集合
若 \(u,v \in SS(w)\) ,且 \(|u|<|v|\),则\(u\)是\(v\)的border,且\(2|u|<|v|\)
考虑两个相邻的最小后缀,\(i,j\),若\(|j|<|i|<2|j|\),则\(i=AAB,j=AB\),其中\(B\)是\(A\)的严格前缀。考虑\(k=B\),发现最小后缀一定在\(i,k\)中产生,所以\(2|u|<|v|\)。
所以\(SS(w)\)的大小是\(log\)级别的。
放个例题:节日庆典
Lyndon Array
最大的\(j\)使得\(s[i,j-1]\)是Lyndon串。
最长的 Lyndon 子串是无交集的。
bool compare(int x, int y) {
int len = LCP(x, y);
return s[x + len] < s[y + len];
}
int ly[MAXN];
void getsuf(bool op = 0) {
ly[n] = n + 1; lim[n] = 0;
for (int i = n - 1; i >= 1; --i) {
ly[i] = i + 1;
while (ly[i] != n + 1 && compare(ly[i], i) == op)
ly[i] = ly[ly[i]];
lim[i] = lim[ly[i]] + 1;
}
}
应用
-
最小表示法
把\(s\)扩展成\(ss\),Lyndon分解后,起点小于\(n\)终点大于\(n\)的串就是最小表示法。 -
寻找每一个前缀的最小后缀
runs
-
k次方串:\(w^k\),\(w\)串重复\(k\)次构成的字符串。
-
run:满足一个子串\(s[i,j]\)周期为\(p\),至少循环两次,且前后都不能扩展,那么我们称\(r = (i,j,p)\)构成\(S\)的一个run,记$e_r = \frac{j-i+1}{p} $为它的指数。
性质
-
周期相同的两个\(runs\ r =(i_1,j_1,p)\)和\(r_2=(i_2,j_2,p)\)的交长度小于\(p\)。
-
任何一个\(run\ r=(i,j,p)\)可以导出\(j-i+2-2p\)个本原平方串,即 \(S[i,j]\) 的所有长度为\(2p\)的子串;同时每个本原平方子串都按照上述规则由唯一的一个\(run\)导出。
-
一个字符串的 \(runs\) 的个数在\(O(n)\)级别。
实现:
在不同字符大小定义下,找到Lyndon 数组,并通过\(LCP\),和\(LCS\)扩展获得。
struct Runs {
char s[MAXN];
int n;
Sa sa, rsa;
int ly[MAXN];
void init(int _n) {
n = _n;
memcpy(sa.s, s, sizeof(s));
reverse(s + 1, s + 1 + n);
memcpy(rsa.s, s, sizeof(s));
reverse(s + 1, s + 1 + n);
sa.getSA(); sa.getHeight(); sa.initST();
rsa.getSA(); rsa.getHeight(); rsa.initST();
}
int LCP(int x, int y) {
return sa.LCP(x, y);
}
int LCS(int x, int y) {
return rsa.LCP(n - x + 1, n - y + 1);
}
bool compare(int x, int y) {
int len = LCP(x, y);
return s[x + len] < s[y + len];
}
vector<array<int, 3>>ans;
void run(int op) {
for (int i = n - 1; i >= 1; --i) {
ly[i] = i + 1;
while (ly[i] && compare(ly[i], i) == op)
ly[i] = ly[ly[i]];
}
for (int i = 1; i <= n; i++) {
if (!ly[i])continue;
int x = i, y = ly[i], p = y - x;
int Lcs = LCS(x, y), Lcp = LCP(x, y);
int l = x - Lcs + 1, r = y + Lcp - 1;
if (Lcs + Lcp > p)
ans.push_back({ l,r,p });
}
}
void solve() {
run(0); run(1);
sort(all(ans));
ans.erase(unique(all(ans)), ans.end());
printf("%d\n", ans.size());
for (auto [x, y, p] : ans)
printf("%d %d %d\n", x, y, p);
}
}run;