Lyndon Word 学习笔记

【定义与性质】

Primitive Word(PW):没有循环节的字符串。

Lyndon Word(LW):字典序严格小于它所有 cyclic-shift 的字符串。

LW 有很多美妙的性质。记 w 为一个字符串。

定义一个新符号 <!:若 a<!b,则 a 字典序小于 ba 不是 b 的前缀。
>! 类似定义。

  1. wLW w 严格小于 w 所有 cyclic-shift。这是定义。

  2. wLW,则 w 无 border。

    w=uvu,则 w=xu=uy,则 w=uy<uxw 小于所有 cyclic-shift)。

    uy<uxy<x,同理可知 x<y。矛盾。

    所以 w 无 border。

  3. 等价定义:wLWw 小于所有真后缀。

    w 的一个后缀为 vw=uv

    uvLW,反证法,如果 uv>v,因为 uv 无 border,所以 uv>!v,则 uv>vu,与 uvLW 矛盾。

    uv<v,有 uv<v<vu

    证毕。

  4. 等价定义:wLWw 任意拆成 w=uvu<v。(u,v 非空)

    换句话说就是 w 的前缀小于后缀。

    :证 uv<vu。若 u<!v,显然;否则 uv 的前缀。

    v=ukr,其中 |r|<|u|。则 w=uk+1r。因为前缀小于后缀,uk+1<r,所以 u<!r。(注意 |r|<|u| 不可能是前缀)

    uv=uk+1v,vu=ukru,显然有 vu>uv

    wLW。那么 w 小于每个真后缀。

    w=uv,有 u<uv<v。(第一个 < 是前缀关系,第二个是真后缀)

  5. u,vLWuvLWu<v

    :由上面的等价定义直接推出。

    :尝试用 "小于每个真后缀" 证明。

    把所有真后缀 suf 分类:vsuf 真后缀的、suf=vsufv 真后缀。

    1. vsuf 真后缀的。则 suf 可以写成 uv 的形式。uu 的真后缀。

      因为 uLW,所以 u 没有 border 且 u<u,所以 u<!u,所以 uv<uv

    2. suf=v。若 u<!v,显然成立;否则 uv 的前缀,设 v=uv。因为 vLW,所以 v<v,(同时加 u)所以 uv<uv=v,得证。

    3. sufv 的真后缀。uv<v<suf

    证毕。

  6. LW 的标准分解定理。若 wLW,取 w 的最小真后缀 v,则 vw 的最长 LW 真后缀,且(记 w=uvu,vLW

    证明:

    若存在 svLW。因为 svLW,知 sv<v,我们就得到了一个比最小真后缀更小的真后缀,矛盾。

    因为 v 是最小真后缀,所以 v 小于所有 v 的真后缀,所以 vLW

    因为 uvLW,所以对于任意 u 的后缀 u,有 uv<uv。因为 |u|>|u|,所以 u<u,所以 uLW

  7. 由 6 引申出一个 LW 的递归定义法,就是每个长度 >1LW 都能拆成两个 LW

【Lyndon 分解】

定理:每一个字符串 s 都能唯一表示s=w1w2wk,满足 wiLWwiwi+1

存在性:

先把 s 拆成 n 个单字符(显然单个字符是 LW),然后只要有两个相邻的前面 < 后面,就合并。这么做是对的,理由来自上面性质的第五点。把 < 都合并了就余下 了。

唯一性:

反证法。考虑两种方案 S,S,假设 SiSi 是第一处不相同的分解位置。

不妨 |Si|>|Si|。记 Si=SiSi+1Sk1Pre(Sk,t),就是一堆整的加一个前缀。要求 k>i

因为 SiLWPre(Sk,t) 是它的一个后缀,所以 Pre(Sk,t)>Si

因为 SiSi 的前缀,所以 Si>Si

所以 Pre(Sk,t)>Si>SiSi+1Sk1Sk。于是得到了 Pre(Sk,t)>Sk,前缀大于本身,矛盾。


Lyndon 分解有它美妙的性质。记 CFL(w)=w1\cdotswmw 的 Lyndon 分解。

  1. wm 是最小后缀。

    考虑 w 的一个后缀 v,如果 vwm 的后缀,因为 wmLW,所以 wm 会比它更小。

    而当 v=wiwi+1wi+2wm,其中 wiwi 的一个后缀。v>wiwiwi+1wm

  2. wm 是最长的 LW 后缀。

    wm 是最小后缀,所以任意比 wm 长的后缀都有 wm 这个更小的真后缀,非 LW

  3. w1 是最长 LW 前缀。

    s=w1w2wkwkwk 的前缀。则 s>w1w2wkwk,于是 s 有了一个更小的后缀。

【Duval 算法求 Lyndon 分解】

  • 定义:准 LW。若 t=wkw,且 wLWww 的前缀,则 t 是准 LW

容易发现,准 LW 就是 LW 的前缀。

引理:两字符 c<c。若 vc 是准 LW,则 vcLW

证明:vc 是准 LW,记 vcuLW。有 vcu 任意一个前缀 < 后缀。考虑 v 的一个前缀 aa<(va)cu<(va)c,所以 vc 的任意一个前缀也 < 后缀。


Duval 算法是一个能 O(|w|) 求出 w 的 Lyndon 分解的算法。

在算法过程中,我们把字符串 w 分成四类:已经输出的、连续相同的 LWLW 的前缀、未处理部分。

黑色部分是已经输出的 s1sg,要求 s1s2sg

蓝色部分是正在处理,尚未确定的部分。t1th 都相等,vti 的前缀。可以把 t1t2thv 看作一个准 LW

红色部分是尚未处理的。

然后我们维护三个指针 i,j,ki 指向蓝色部分的开头,k 指向下一个要处理的字符,j 指向 k|ti|(可以理解为若还想保持 ti 不变,k 指向的字符应该和 j 指向的字符相同)。

  1. wk=wj。若 v 已经等于 ti 了,新开一个 v;否则往 v 里加就好。

  2. wk>wj。把 t1t2thvwk 一起作为一个新的 t1

  3. wk<wj。输出 t1th(变成 s 部分),k 指向 v 开头重新跑

k 已经指向结尾之后,但是 v 非空,把 k 指向 v 开头重新跑。

代码很短。

点击查看代码
#include <bits/stdc++.h>

using namespace std;

int n;
string s;
int i, j, k;

int main() {
	int ans = 0;
	cin >> s;
	n = s.size();
	s = ' ' + s;
	i = 1;
	while (i <= n) {
		j = i;
		k = i + 1;
		while (s[k] >= s[j]) {
			if (s[k] == s[j])
				j++;
			else
				j = i;
			k++;
		}
		while (i <= j) {
			ans ^= (i + (k - j) - 1);
			i += k - j;
		}
	}
	cout << ans << endl;
	return 0;
}

【Lyndon 分解的各种应用】

求字符串的最小表示法

若对 s 求最小表示法,令 s=ssCFL(s) 中包含 sn 的 LW 开始位置,就是 s 最小表示法的开始位置之一。

引理:设 CFL(w)=w1wmwi=w[Li,Ri]。则以 [1,Ri] 作为起点的后缀中,以 Li 为起点的是最小的。

证明:考虑起点 xLi

  1. Li<xRi。因为 wiLW,所以 wi<w[x,Ri],所以 wi<!w[x,Ri](长度更长还小)。所以 suf(Li)<suf(x)

  2. x<Li,设 x[Lj,Rj]wj=w[x,Rj]

    wjwjwj+1wi,所以 wiwj

    wi<!wj,显然;否则 wiwj 的前缀。

    那么比 suf(x),suf(Li) 其实就是比 suf(x+RiLi+1),suf(Li+1)

    如此循环下去,可以用归纳法到 suf(x+?),suf(Lm),而 wm 是最小后缀。

证完引理,回到原来的算法。在 s 中,最小表示法就是 s 的一个长度为 |s| 的子串。

要比较它们,也相当于比较对应开始位置在 s 里的后缀的大小关系。最小者,肯定前 |s| 个字符也是最小的。

Lyndon Word 生成算法/找后继

||=k。要求按照字典序从小到大生成所有长度 n,字符集大小为 k 的 LW。

把字符集看作 k 进制数。最小的 LW 显然是 0。如果我们能找某个 LW 的后继,就可以从 0 生成所有的了。

而可以找到 wLW 的后继。这么做:

  1. 截取 R(w) 的前 n 个字符,记为 w。(例如 n=3,w=01,w=010

  2. 删除 w 末尾连续的最大字符。(例如 w=012,k=3,删完之后 w=01

  3. w+1,就是 k 进制数意义下的。因为已经删完了末尾的最大字符,所以肯定不会进位。

例如 ={0,1,2},n=3,可以生成出:0,001,002,01,011,012,02,021,022,1,112,12,122,2

LW 计数

lenn,||=k 的 LW 个数。

可以等价到另外一个问题:项链计数,要求没有循环节,称这个为问题 1。
考虑问题 2:项链计数,允许有循环节。

记问题 1 的答案为 Sn,问题 2 的答案为 Tn。(k 是固定的)

考虑集合 A 包含所有 n 个珠子的项链,显然 |A|=kn

考虑集合 B(多重集)包含 Tn 里所有项链的 cyclic-shift。

B 的大小比 A 大,因为有的项链重复出现了。(例如 0101 出现两次)
同时 |B|=nTn

考虑算 |B|。用 A 中每个元素在 B 出现次数之和除以 n 可得 Tn。记 S 为出现次数之和

S=(a0,,an1)Ai=0n1[a0a1an1=aian1a0ai1]=i=0n1(a0,,an1)A[]=i=0n1kgcd(n,i) (相当于枚举循环节长度)=dnφ(d)knd

所以 Tn=1ndnφ(d)knd。另外 Tn=dnSd,即枚举循环节。

推导一下。记 K(x)=kx.

Tn=1ndnφ(d)kndnTn=φKnT=idμK

同时 Tn=dnSdnT=n(S1)

待证明:经过一些运算,nT=id(nS)

所以 nT=idμK=id(nS),所以 nS=μK,所以 Sn=1ndnμ(d)knd

【利用 Duval 算法中间结果】

在跑 Duval 的过程中同步计算答案。

求每个前缀的最小后缀

ans[i]w[1i] 最小后缀开始位置。

  1. w[k]=w[j],则 ans[k]=ans[j]+(kj)。可以类比着来看。

  2. w[k]>w[j]ans[k]=i

  3. w[k]<w[j],这种情况不管,因为 k 回溯之后会处理的。

posted @   FLY_lai  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示