【学习笔记】Lyndon Word
定义
若一个字符串\(s\)的最小后缀是它自己,我们称其为\(Lyndon\)串。
等价定义:若\(s\)是其所有循环重构串中字典序最小的串,则\(s\)是\(Lyndon\)串。
\(Lyndon\)分解
任意字符串\(s\),都可以唯一分解成\(s=s_1s_2...s_k\),其中\(\forall s_i\)为\(lyndon\)串,且\(s_i≥s_{i+1}\)
存在性证明
先来看一个引理:
引理1:
如果\(u\),\(v\)都是\(Lyndon\)串,并且字典序\(u<v\),则\(uv\)也是\(Lyndon\)串。
(感觉比较显然吧,但还是证明一下
1.\(len(u)≥len(v)\)时
因为\(v>u\),所以\(v\)>\(uv\),又因为\(v\)是\(Lyndon\)串,所以\(v\)的所有非自己的后缀都大于\(v\),所以开头在\(v\)的部分的后缀都大于\(uv\)。因为\(u\)也是\(Lyndon\)串,所以\(u\)的所有后缀都大于\(u\),\(uv\)的开头在\(u\)部分的后缀也就大于\(uv\)(在\(u\)部分的比较就已经大于)
2.\(len(u)<len(v)\)时
若\(u\)不是\(v\)的前缀,那么在\(len(u)\)之前就有\(v>u\),所以\(v>uv\),同上可证。
若\(u\)是\(v\)是的前缀,那么\(v>uv\),同上可证。
有了引理之后再来证明。
首先,\(s\)中的每一个单个字符都是一个\(Lyndon\)串,初始时每段都只有一个字符。
从左往右开始合并,左边已经合并了的字符串\(s_i\)大于当前字符\(s[j]\)的话就把\(s[j]\)并入\(s_i\),根据引理1可得这样合并后分出来的每一段都是\(Lyndon\)串。
而每次比较的时候\(s_i<s[j]\)都并进去了,所以没有并进去的话,就满足每一段\(s_i>s_{i+1}\)
唯一性证明
(其实这个也比较显然吧,根据上面我们是能并就并了,没有其他可操作的空间,所以就只有一种
这个我们用反证法。
假设对于一个字符串\(s\)有两种\(Lyndon\)分解
我们记第一次不同的位置为\(i\),设\(len(s_i)>len(s_{i}')\)
设\(s_i=s_{i}'s_{i+1}'...s_{k}'s_{k+1}'[1...l]\)
\(s'_{k+1}[1...l]\)指第\(k+1\)段串中的前缀\(1..l\)
可以得到以下关系:
\(s_i<s_{k+1}’[1...l]\):\(s_i\)是\(Lyndon\)串,所以它的后缀大于它
\(s_{k+1}’[1...l]≤s_{k+1}’\):一个字符串的前缀小于等于它自己
\(s_{k+1}’≤s_i'\):\(Lyndon\)分解中,前面的段的字典序大于后面的段的字典序。
\(s_i'<s_i\):\(s_i'\)是\(s_i\)的前缀,一个字符串的前缀小于等于它自己
综合上述不等式,可以得到\(s_i<s_i\),产生矛盾,所以假设不成立
\(Duval\)算法
是一种在\(O(n)\)时间复杂度之内求出一个串的\(Lyndon\)分解的算法。
引理2:
若字符串\(v\)和字符\(c\)满足\(vc\)是某个\(Lyndon\)串的前缀,则对于字符\(d>c\)有\(vd\)是\(Lyndon\)串。
(觉得还是可以感性理解
证明:
设\(Lyndon\)串为\(vct\)
根据\(Lyndon\)串的性质可得,\(\forall i∈[2,len(v)],v[i...len(v)]ct≥vct\).
根据\(i\)的取值范围,\(v[i.. .len(v)]\)长度最大是\(len(v)-1\),那么\(v[i...len(v)]c\)的长度小于等于\(v\)。
所以\(v[ i...len(v)]c\)要么是\(v\)的前缀(\(v[i...len(v)]ct\)在\(t\)的部分大于\(vct\)),要么大于\(v\),总之\(v[i..len(v)]c\)不可能小于\(v\),否则\(v[i...len(v)]ct≥vct\)是不会成立的。
所以可以得到:\(v[i...len(v)]c≥v\)
那么:\(v[i...len]d>v[i...len]c>v\),所以\(v[i...len]>vd\)
算法流程
在这个算法中,我们维护三个变量\(i,j,k\)
其中,\(s[1...i-1]=s_1s_2...s_g\)是已经固定下来的分解,满足每一段\(l∈[1,g-1],s_l>s_{l+1}\)
\(s[i...k-1]=t^hv,h≥1\)是没有固定的分解,并且\(t\)是\(Lyndon\)串,\(v\)是\(t\)的一个前缀(可为空)。
\(j=k-|t|\),当前扫到未处理的字符是\(s[k]\)
分三类情况讨论:
\(s[k]==s[j]\):继续往前扫,保持周期
\(s[k]>s[j]\):根据引理2,我们将\(t^hvs[k]\)合并起来,是一个\(Lyndon\)串(是向前合并,\(t\)和\(vs[k]\)合并起来,再将\(tvs[k]\)与前面的\(t\)合并(注意相等的两个串并不能用引理1进行合并,因为\(uu\)的后缀\(u\)不大于$uu $)。
这里的合并是相当于把\(t^hvs[k]\)当成一个新的\(t\),不是就把它当成\(Lyndon\)分解里的一段了,它还有可能和后面的串拼在一起形成一个新的\(Lyndon\)
\(s[k]<s[j]\):\(t^h\)的分解被固定为\(h\)个\(Lyndon\)串,算法从\(v\)的开头重新开始。
复杂度分析
\(i\)只会往右移,\(k\)移动的距离不超过\(i\)右移的距离(\(k\)移动\(|v|\),而\(i\)移动\(|t^hv|\))
复杂度为\(O (n)\)
Code View
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
#include<iostream>
#include<cstdio>
#include<cmath>
#include<map>
using namespace std;
#define N 5000005
#define INF 0x3f3f3f3f
#define LL long long
int rd()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48); c=getchar();}
return f*x;
}
int n,ans;
char s[N];
int main()
{
scanf("%s",s+1);
n=strlen(s+1);
for(int i=1;i<=n;)
{
int j=i,k=i+1;
while(k<=n&&s[j]<=s[k])
{
if(s[j]<s[k]) j=i;//合并
else j++;
k++;
}
while(i<=j)
{
ans=ans^(i+k-j-1);
i+=k-j;
}
}
printf("%d\n",ans);
return 0;
}
——注:博客参考了金策的字符串算法选讲内容