Lydon 分解与最小表示法
阅读原文时间:2023年07月09日阅读:1

我们定义一个串是 \(\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}\) 串。

这个算法可以在 \(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;
}

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章