一.Lyndon串的定义.
对于两个串s1与s2,令 , 仅当 且 , 仅当 ,或 且 .
一个字符串s为Lyndon串仅当s在它的所有后缀中最小.
二.Lyndon串的一个性质.
性质1:当u,v为Lyndon串且u<v时,uv(u与v相连接)也为Lyndon串.
证明如下:
首先显然uv大于任意一个u的后缀与v连接.
然后因为
,所以
.
综上所述,uv小于所有它的后缀,即uv为一个Lyndon串.
证毕.
三.Lyndon word问题.
Lyndon word问题是指,将一个字符串s分解成 ,其中 为Lyndon串, ,求拆分方案.
这个问题首先具有存在性,证明:
首先当一个串只有一个字符时,显然这个串为一个Lyndon串.
当一个串S不是Lyndon串时,它显然可以被分成
个单个字符组成的字符串合并的形式.
根据性质1,这些串中只要有
的情况,就可以被合并成一个新的Lyndon串.
证毕.
这个问题也具有唯一性,用反证法证明如下:
设若有两种方案,设两种方案不同的位置为
,两种方案分别为
与
,且
.
我们令
,其中
表示
的一个前缀.
根据定义,
.
证毕.
四.Duval算法.
Duval算法是用来在线性求解拆分方式的一种算法,它需要用到下面这个性质2:
当字符串s与字符c组成的字符串sc是某个Lyndon串的前缀时,对于字符
满足sd为Lyndon串.
证明如下:
首先显然对于一个Lyndon串的前缀s
,即s小于等于它的所有后缀.
所以s与sc必然都满足小于等于它们的所有后缀,加入一个大于c的字符自然也就变成了Lyndon串.
证毕.
那么我们就可以开始讲解Duval算法了,Duval算法只需要维护三个指针i,j,k就可以求出一个串的Lyndon分解.
首先,i表示当前未确定拆分的第一个点,即 已经被确定了拆分方式.
然后,我们设 ,其中 都是Lyndon串,v必须是一个 的前缀,也就是说 必须是由串 循环构成的.
这时我们再维护一个指针
,也就是k对应在
这个串的字符.很容易发现我们要维护串
是否可被分成一个Lyndon串循环构造的性质,分三种情况:
1.
,也就是说它还是可以被循环构造的,这时候让j和k自增1就好了.
2.
,显然
为一个Lyndon串,所以将这个串合并就好,即让j变成i,k自增1.
3.
,发现这时候合并并不能变成Lyndon串,所以停止操作记录每一个位置,并将i指针变成v的开头.
时间复杂度为线性是因为一个字符最多会被扫到两边,所以时间复杂度为 .
代码如下:
void Lyndon_word(){
int j,k;
for (int i=1;i<=n;){
for (j=i,k=i+1;k<=n&&s[k]>=s[j];++k) //一直循环直到串结束或情况3出现
s[k]>s[j]?j=i:++j; //情况1与情况2
for (;i<=j;i+=k-j)
x[++ts]=i+k-j-1; //记录右端点
}
}
五.例题与代码.
例题:LOJ129.
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=1<<20;
char s[N+9];
int n,x[N+9],ts;
void Lyndon_word(){
int j,k;
for (int i=1;i<=n;){
for (j=i,k=i+1;k<=n&&s[k]>=s[j];++k) //一直循环直到串结束或情况3出现
s[k]>s[j]?j=i:++j; //情况1与情况2
for (;i<=j;i+=k-j)
x[++ts]=i+k-j-1; //记录右端点
}
}
Abigail into(){
scanf("%s",s+1);
n=strlen(s+1);
}
Abigail work(){
Lyndon_word();
}
Abigail outo(){
for (int i=1;i<=ts;++i)
printf("%d ",x[i]);
}
int main(){
into();
work();
outo();
return 0;
}