我们定义一个串是 \(\text{Lyndon}\) 串,当且仅当这个串的最小后缀就是这个串本身。

该命题等价于这个串是它的所有循环表示中字典序最小的。

引理 1:如果 \(u\) 和 \(v\) 都是 \(\text{Lyndon}\) 串并且 \(u<v\),则 \(uv\) 也是 \(\text{Lyndon}\) 串。

证明:

1、若 \(\operatorname{len}(u)\ge\operatorname{len}(v)\)

这时,\(u\) 和 \(v\) 这两个串在 \(\operatorname{len}(v)\) 之前就出现了不同的字符,所以有 \(v>uv\),又因为 \(v\) 是 \(\text{Lyndon}\) 串,所以 \(v\) 的所有后缀都大于 \(v\),所以 \(uv\) 的所有后缀都大于 \(uv\),故 \(uv\) 是 \(\text{Lyndon}\) 串。

2、若 \(\operatorname{len}(u)<\operatorname{len}(v)\)

若 \(u\) 不是 \(v\) 的前缀,那么有 \(v>uv\),得证。

若 \(u\) 是 \(v\) 的前缀,那么如果 \(v<uv\),则必有 \(v[\operatorname{len}(u)+1:]<v\)(也就是各自去掉了前 \(|u|\) 个字符),矛盾。


我们定义一个串 \(S\) 的 \(\text{Lyndon}\) 分解为一个字符串序列 \(A_1,A_2,\dots,A_m\),满足:

  • \(\forall i \in [1,m]∀i∈[1,m]\),满足 A_i是 \(\text{Lyndon}\) 串。

  • \(\forall i \in [1,m-1]∀i∈[1,m−1]\),满足 \(A_i\ge A_{i+1}\)

可以证明这种划分存在且唯一。

存在性证明:

初始令 \(m=|S|\) 并且 \(A_i=S[i]\),然后每次不断找到 \(A_i<A_{i+1}\) 并且合并为一个串。最后一定能使得所有的 \(A_i\ge A_{i+1}\)

实际上,通过这个证明,我们可以对其建出 \(SAM\) ,再通过 \(O(1)\) 求 \(parent\) 树上的 \(lca\) 比较两个后缀的大小即可在时间空间复杂度均视为 \(O(n)\) 的情况下求出一个字符串的 \(Lydon\) 分解。

引理2:若字符串 \(v\) 和字符 \(c\) 满足 \(vc\) 是某个 \(\text{Lyndon}\) 串的前缀,则对于字符 \(d>c\) 有 \(vd\) 是 \(\text{Lyndon}\) 串。

证明:

设该 \(\text{Lyndon}\) 串为 \(v+c+t\)。

则 \(\forall i \in [2,|v|],v[i:]+c+t>v+c+t\),也就是说 \(v[i:]+c\ge v\)。

所以 \(v[i:]+d>v[i:]+c\ge v\)。

同时因为 \(c>v[1]\),我们有 \(d>c>v[1]\)。

故 \(v+d\) 是一个 \(\text{Lyndon}\) 串。

Duval 算法

这个算法可以在 \(O(n)\) 时间复杂度,\(O(1)\) 空间复杂度内求出一个串的 \(\text{Lyndon}\) 分解。

该算法中我们仅需维护三个变量 \(i,j,k\)。

维持一个循环不变式:

  • \(s[:i-1]=s_1s_2\cdots s_g\) 是固定下来的分解,也就是 \(\forall l\in[1,g],s_l\) 是 \(\text{Lyndon}\) 串且 \(s_l>s_{l+1}\)

  • \(s[i,k−1]=t^h+v\times [h>1]\) 是没有固定的分解,满足 \(t\) 是 \(\text{Lyndon}\) 串,且 \(v\) 是 \(t\) 的可为空的不等于 \(t\) 的前缀,且有 \(s_g>s[i,k-1]\)

如下图:

当前读入的字符是 \(s[k]\),令 \(j=k-|t|\)。

分三种情况讨论:

当 \(s[k]=s[j]\) 时,直接 \(k\leftarrow k+1,j\leftarrow j+1\),周期 \(k-j\) 继续保持。

当 \(s[k]>s[j]\) 时,由引理 2 可知 \(v+s[k]\) 是 \(\text{Lyndon}\) 串,由于 \(\text{Lyndon}\) 分解需要满足 \(s_i\ge s_{i+1}\),所以不断向前合并,最终整个 \(t^h+v+s[k]\) 形成了一个新的 \(\text{Lyndon}\) 串。

当 \(s[k]<s[j]\) 时,\(t^h\) 的分解被固定下来,算法从 \(v\) 的开头处重新开始。

复杂度分析:\(i\) 只会单调往右移动,同时 \(k\) 每次移动的距离不会超过 \(i\) 移动的距离,所以时间复杂度是 \(O(n)\) 的。

【模板】Lyndon 分解

点击查看代码
#include<bits/stdc++.h>
using namespace std;
char s[5000005];
int n,ans;
int main(){
scanf("%s",s+1);
n=strlen(s+1);
int i=1;
while(i<=n){
int j=i,k=i+1;
while(k<=n&&s[k]>=s[j]){
if(s[k]>s[j])j=i;
else j++;k++;
}
while(i<=j){
ans^=(i+k-j-1);
i+=k-j;
}
}printf("%d",ans); return 0;
}

最小表示法

一个字符串的最小表示定义为其所有循环同构中字典序最小的串。

最小表示法可以使用 \(\text{Lyndon}\) 分解求出。

对于长度为 \(n\) 的字符串 \(s\),设 \(t=s+s\),对 \(t\) 进行 \(\text{Lyndon}\) 分解,找到首字符位置 \(\le n\) 且最大的 \(\text{Lyndon}\) 串,这个串的首字符即最小表示法的首字符。

【模板】最小表示法

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int s[5000005];
int n,ans;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&s[i]);
for(int i=1;i<=n;i++)s[i+n]=s[i];
int i=1;
while(i<=n){
int j=i,k=i+1;
while(k<=2*n&&s[k]>=s[j]){
if(s[k]>s[j])j=i;
else j++;k++;
}
while(i<=j){
i+=k-j;
if(i<=n)ans=i;
}
}
for(int i=0;i<n;i++)printf("%d ",s[i+ans]); return 0;
}

[JSOI2019]节日庆典

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n;
char s[3000005];
int z[3000005];
void exkmp(){
int l=0,r=0;z[1]=n;
for(int i=2;i<=n;i++){
if(r>i)z[i]=min(z[i-l+1],r-i+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;
}
return ;
}
vector<int>now,nxt;
int main(){
scanf("%s",s+1);
n=strlen(s+1);exkmp();
for(int i=1;i<=n;i++){
now.push_back(i);
nxt.clear();
for(int j=0;j<now.size();j++){
int p=now[j];
bool flag=1;
while(!nxt.empty()){
int q=nxt.back();
if(s[i]>s[q+i-p])flag=0;
if(s[i]>=s[q+i-p])break;
nxt.pop_back();
}
if(flag&&(nxt.empty()||(i-p+1<p-nxt.back())))nxt.push_back(p);
}
now=nxt;
int pos=now[0];
for(int j=1;j<now.size();j++){
int x=now[j],k=pos+i-x;
if(z[k+1]>=i-k){
register int l=i-k;
if(z[l+1]<x-l-1&&s[l+z[l+1]+1]<s[z[l+1]+1])pos=x;
}
else if(s[z[k+1]+1]<s[k+z[k+1]+1])pos=x;
}
printf("%d ",pos);
}
return 0;
}

Lydon 分解与最小表示法的更多相关文章

  1. HDU 4162 Shape Number (最小表示法)

    题意:给你一串n个数,求出循环来看一阶差的最小字典序:数字串看成一个顺时针的环,从某一点开始顺时针循环整个环,保证字典序最小就是答案 例如给你 2 1 3 就会得到(1-2+8 注意题意负数需要加8) ...

  2. POJ 1635 树的最小表示法/HASH

    题目链接:http://poj.org/problem?id=1635 题意:给定两个由01组成的串,0代表远离根,1代表接近根.相当于每个串对应一个有根的树.然后让你判断2个串构成的树是否是同构的. ...

  3. HDU 2609 最小表示法

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2609 题意:给定n个循环链[串],问有多少个本质不同的链[串](如果一个循环链可以通过找一个起点使得和 ...

  4. HDU 4162 最小表示法

    题目:http://acm.hdu.edu.cn/showproblem.php?pid=4162 题意:给定一个只有0-7数字组成的串.现在要由原串构造出一个新串,新串的构造方法:相邻2个位置的数字 ...

  5. POJ 1509 最小表示法

    题目链接:http://poj.org/problem?id=1509 题意:给定一个字符串,求一个起点使字符串从该起点起的字符串字典序最小[题目的字符串起点从1开始] 思路:最小表示法模板题 #de ...

  6. UVA 1314 最小表示法

    题目链接:http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=36117 题意:给定长度为n的字符串,求一个起点使字符串从该起点起的 ...

  7. hdu String Problem(最小表示法入门题)

    hdu 3374 String Problem 最小表示法 view code#include <iostream> #include <cstdio> #include &l ...

  8. hdu5442(2015长春赛区网络赛1006)后缀数组+KMP /最小表示法?

    题意:给定一个由小写字母组成的长度为 n 的字符串,首尾相连,可以从任意一个字符开始,顺时针或逆时针取这个串(长度为 n),求一个字典序最大的字符串的开始字符位置和顺时针或逆时针.如果有多个字典序最大 ...

  9. 【KMP】【最小表示法】NCPC 2014 H clock pictures

    题目链接: http://acm.csu.edu.cn/OnlineJudge/problem.php?id=1794 题目大意: 两个无刻度的钟面,每个上面有N根针(N<=200000),每个 ...

随机推荐

  1. wireshark、tcpdump使用笔记

    最近使用wireshark抓包icmp协议,过滤的命令如下所示: ip.addr eq 192.168.20.54 and ip.addr eq 192.168.50.131 and (icmp) 如 ...

  2. 攻防世界-MISC:2017_Dating_in_Singapore

    这是MISC高手进阶区的题目:题目如下: 点击下载附件一,得到一张pdf图片,除此之外就只有题目给的字符串了,不知道是什么意思(查看了一下WP)原来每一串通过"-"隔开的字符串代表 ...

  3. Kafka Kerberos 安全认证

    本主要介绍在 Kafka 中如何配置 Kerberos 认证,文中所使用到的软件版本:Java 1.8.0_261.Kafka_2.12-2.6.0.Kerberos 1.15.1. 1. Kerbe ...

  4. Neo4j数据和Cypher查询语法笔记

    Cypher数据结构 Cypher的数据结构: 属性类型, 复合类型和结构类型 属性类型 属性类型 Integer Float String: 'Hello', "World" B ...

  5. [笔记] K-D Tree

    一种可以 高效处理 \(k\) 维空间信息 的数据结构. 在正确使用的情况下,复杂度为 \(O(n^{1-\frac{1}{k}})\). K-D Tree 的实现 建树 随机一维选择最中间的点为当前 ...

  6. 配置Linux的时钟同步

    公众号关注 「开源Linux」 回复「学习」,有我为您特别筛选的学习资料~ Ubuntu系统默认的时钟同步服务器是ntp.ubuntu.com,Debian则是0.debian.pool.ntp.or ...

  7. go convert slice to struct

    Question: in golang how to convert slice to struct scene 1:use reflect convert slice to struct func ...

  8. Java实现飞机大战游戏

    飞机大战详细文档 文末有源代码,以及本游戏使用的所有素材,将plane2文件复制在src文件下可以直接运行. 实现效果: 结构设计 角色设计 飞行对象类 FlyObject 战机类 我的飞机 MyPl ...

  9. Dockerfile 使用 SSH

    如果在书写 Dockerfile 时,有些命令需要使用到 SSH 连接,比如从私有仓库下载文件等,那么我们应该怎么做呢? Dockerfile 使用 SSH Dockerfile 文件配置 为了使得 ...

  10. freeswitch使用mod_shout模块播放mp3

    概述 freeswitch 在对VOIP语音通话中,可以通过playback命令播放IVR语音文件. 默认情况下,freeswitch支持wav文件,也可以直接播放VOIP中常见编解码的G711文件. ...