定义
若一个字符串(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
",ans);
return 0;
}
——注:博客参考了金策的字符串算法选讲内容