把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【学习笔记】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;
}

——注:博客参考了金策的字符串算法选讲内容

posted @ 2020-08-09 22:12  Starlight_Glimmer  阅读(200)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end