Lyndon 分解学习笔记

一些定义

s1+s2s1s2 均表示字符串拼接,sk 表示字符串 s 重复 k 遍。<,>,, 均表示字典序的比较。

Lyndon 串:定义一个串 s 是 Lyndon 串当且仅当 s 的字典序严格小于 s 的所有后缀。特别的,一个字符也是 Lyndon 串。

Lyndon 分解:我们将串 s 划分为 w1+w2++wk,其中所有 wi 均为 Lyndon 串,且 i[1,k1],wiwi+1。这一组 w 成为 s 的 Lyndon 分解。可以证明,对于任意字符串 s,这样的分解存在且唯一。

Lyndon 分解

  • 若串 u,v 为 Lyndon 串且 u<v,则 uv 也为 Lyndon 串。

证明:若 |u|>|v|u 不是 v 的前缀,直接比较即可证明;否则设 v=uw,因为 v 是 Lyndon 串,w 的所有后缀都 >v,这样 w 的所有后缀都 >uv

这样我们就得到了一个暴力的 Lyndon 串分解的做法:

先把整个串分解为 |s| 个字符,每个字符都是一个 Lyndon 串。接下来每次找到一个 wi<wi+1 的地方,把这两个字符串合并,不断合并直到无法再合并为止。最终得到 w1w2wk

  • 若字符串 v 和字符 c,满足 vc 是 Lyndon 串的前缀,则对于字符 d>cvd 是 Lyndon 串。

Duval 算法

我们将我们要分解的串 S 分成三个部分:s1s2s3,其中 s1 是已经分解完成的部分,s2 是正在分解的部分,s3 是未分解的部分。

我们需要保证任意时刻,s2=ut+u,其中 u 是 Lyndon 串,uu 的前缀(可以为空)。每次我们将 s3 中的第一个字符 Sks3 中加入 s2 中。令 j=k|u|

  • Sk=Sj,直接将 Sk 加入 s2 即可,唯一的影响是 u 变大,可能会令 t+1u 变成空串。
  • Sk>Sj,根据上面的性质 2,这时 uSk 是一个 >u 的 Lyndon 串。再根据性质 1,这个 Lyndon 串会不断往前合并,也就是 utuSk 变成一个新的 Lyndon 串,作为新的 s2u
  • Sk<Sj,这时我们可以确定 utt 个串不会再被合并,可以直接确定他们在最终的 Lyndon 分解中。s1+=ut,令 u 中的元素重新返回 s3,重新开始分解。

容易发现复杂度是均摊 O(n) 的。实际实现的时候我们维护三个指针 i,j,ki 表示 s2 的开头位置,j,k 和上面相同,这样 kj 就表示了 |u|。三个情况对 i,j,k 的变化是

  • jj+1,kk+1
  • ji,kk+1
  • ii+kikj(kj),然后 ji,ki+1
view code
#include <bits/stdc++.h>
using namespace std;
const int N=1e7+5;
char s[N];
int n,ans;
inline void build(int l,int r){ans^=r;}
int main(){
scanf("%s",s+1);
n=strlen(s+1);
int i=1,j,k;
while(i<=n){
j=i;k=i+1;
for(;k<=n;++k){
if(s[k]==s[j])++j;
else if(s[k]>s[j])j=i;
else break;
}
int len=k-j;
while(i+len-1<k)build(i,i+len-1),i+=len;
}
printf("%d\n",ans);
return 0;
}

Lyndon 分解与最小表示法

对于长度为 n 的串 s,我们需要找到他的最小表示法。

  • 做法 1:我们将串 s 倍长,变为 ss。我们求出 ss 的 Lyndon 分解中,覆盖 n 的那个。以它的左端点作为 Lyndon 分解的左端点即可。
  • 做法 2

[JSOI2019]节日庆典

我们要求一个字符串的所有前缀的最小表示法。

我们先把串 s 的 Lyndon 分解写成 s=w1t1w2t2wntn,其中 w1,w2,,wn 均为 Lyndon 串,且 w1>w2>>wn。这时,我们有结论:最小表示法一定是某个 witi 的开头。

更进一步地,我们发现如果 wn1tn1 的开头比 wntn 的开头优的话,因为 wn1>wn 所以 wn1 一定是 wn 的前缀。以此类推,如果 witi 是最终的答案的话,那么 wi 要是 wi+1 的前缀,wi+1 要是 wi+2 的前缀, ……。

这样可能的起点数量就只有 O(logn) 个,我们只需要比较这 O(logn) 个点谁更优即可。又因为这 O(logn) 个点都有前缀关系,所以我们并不需要求出一个串任意两个后缀的 LCP,我们只需要求一个后缀和整个串的 LCP,扩展 kmp 即可。

这样我们得到了一个 O(nlogn) 的做法,但是我们还可以做到更优。

考虑 Duval 算法,在 Lyndon 分解的同时求出答案。s2=ut+u,其中 u 是 Lyndon 串,uu 的前缀(可以为空)。每次我们将 s3 中的第一个字符 Sks3 中加入 s2 中。令 j=k|u|

  • Sk<Sj,显然在 k 之后的所有位置中,选择 u 前面的任何一个位置作为最小表示法的起点都不如 u,所以我们直接把 ut 忽略不考虑,Duval 算法继续分解 u 即可。
  • Sk>Sj,这时 utuSk 变成一个新的 Lyndon 串,而 u 前面的所有位置已经确定是不优的,那么我们唯一的选择就是最后一个 Lyndon 串 utuSk 的起点作为 k 的答案。
  • Sk=Sj,此时有多种选择:选择 ut 的开头或者选择 u 中的某个位置。(根据上面所说的,我们不会选择 ut 中除了 ut 的开头的其它位置)。uu 的一个前缀,所以选择 u 中的某个位置这部分的答案是之前算过的,我们取 j 对应的位置(即 i(jansj))即可。这两种情况比较一下取更优的一个即可。

和上面一样,我们要比较的东西有前缀关系,我们只需要求一个后缀和整个串的 LCP,扩展 kmp 即可。这样总复杂度就是 O(n) 的了。

view code
#include <bits/stdc++.h>
using namespace std;
namespace iobuff{
const int LEN=1000000;
char in[LEN+5],out[LEN+5];
char *pin=in,*pout=out,*ed=in,*eout=out+LEN;
inline void pc(char c){
pout==eout&&(fwrite(out,1,LEN,stdout),pout=out);
(*pout++)=c;
}
inline void flush(){fwrite(out,1,pout-out,stdout),pout=out;}
template<typename T> inline void putint(T x,char div='\n'){
static char s[20];
static int top;
top=0;
x<0?pc('-'),x=-x:0;
while(x) s[top++]=x%10,x/=10;
!top?pc('0'),0:0;
while(top--) pc(s[top]+'0');
pc(div);
}
}
using namespace iobuff;
const int N=3e6+5;
int z[N],n;
char s[N];
inline void exkmp(){
z[1]=n;
for(int i=2,r=0,l=1;i<=n;++i){
if(i<=r)z[i]=min(r-i+1,z[i-l+1]);
while(i+z[i]<=n&&s[i+z[i]]==s[z[i]+1])++z[i];
if(i+z[i]-1>r)l=i,r=i+z[i]-1;
}
}
inline int getmn(int x,int y,int r){
if(x>y)swap(x,y);
int p1=x+(r-y+1),len1=z[p1];
if(len1>=r-p1+1){
len1=r-p1+1;
int p2=len1+1,len2=z[p2];
if(p2+len2-1>=y)return x;
return s[p2+len2]<s[len2+1]?y:x;
}else{
if(len1>=y)return x;
return s[len1+1]<s[p1+len1]?y:x;
}
}
int ans[N];
int main(){
scanf("%s",s+1);
n=strlen(s+1);
exkmp();
for(int i=1,j,k;i<=n;){
if(!ans[i])ans[i]=i;
for(j=i,k=i+1;s[k]>=s[j];++k){
int len=k-j;
if(s[k]==s[j]){
if(!ans[k]){
int u=(k-i)%len+i;
if(ans[j]>=i)ans[k]=getmn(k+ans[j]-j,i,k);
else ans[k]=i;
}
++j;
}else{
if(!ans[k])ans[k]=i;
j=i;
}
}
int len=k-j;
while(i+len-1<k)i+=len;
}
for(int i=1;i<=n;++i)putint(ans[i],' ');
flush();
return 0;
}
posted @   harryzhr  阅读(121)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示