Lyndon分解

 

比较偏的算法,仅用于丧心病狂卡后缀数组的时候。

推荐看xht37 - Lyndon 分解 学习笔记 

由于参考的文章写的很详细,所以这篇是补充一点自己的认识。

 


 

~ 两个概念 ~

 

Lyndon串:字符串ss本身即为所有后缀中字典序最小的。即有s<s[i..n],i>1s<s[i..n],i>1(字典序比较)。

Lyndon分解:将字符串ss按顺序不重叠地分解成s1+...+sms1+...+smmm个Lyndon串,且保证sisi+1,1i<msisi+1,1i<m

 

在很多后缀数组的题目中会出现Lyndon串的概念,不过并不怎么涉及Lyndon分解。

其实Lyndon分解也是可以用后缀数组进行求解的:首先smsm的起点必然是rnk=1rnk=1的位置(即sa[1]sa[1]),接着考虑sm1+smsm1+sm则是 除了smsm的后缀中 字典序最小的后缀...可以用一个数组标记rnk=irnk=i的后缀是否已经被包含,那么每次向后循环找到第一个未被包含的rnkrnk(从后向前看,每个Lyndon串起点的rnkrnk是单调增的)将其设为新增Lyndon串的开头即可,接着把该Lyndon串中的位置全标记为“已被包含”即可。

不过求后缀数组是O(nlogn)O(nlogn)的,在真实情况中会有人卡。于是需要用到一种O(n)O(n)的做法。

 


 

~ Duval算法 ~

 

在Lyndon分解问题中,我们并不需要求出所有后缀间的大小关系,只要能够选出每一个Lyndon串即可。

于是Duval算法考虑了一个字符串ss为Lyndon串的条件:

1. s[1]s[1]必然为ss中最小的字符。

2. 如果s[1]s[1]ss中多次出现,那么以每个s[1]s[1]字符为起点的子串必然小于开头的子串。

举两个栗子:s=abcabcds=abcabcd是Lyndon串,因为s[1..4]=abca<s[4..7]=abcds[1..4]=abca<s[4..7]=abcd;而s=abcabcs=abcabc不是Lyndon串,因为s[1..4]=abca>s[4..6]=abcs[1..4]=abca>s[4..6]=abc(可以将s[4..6]s[4..6]看做s[4..7]s[4..7],其中s[7]=0s[7]=0)。

 

于是Duval算法考虑每次出现s[1]s[1]都进行比较。

i,j,ki,j,k三个指针,其中ii为当前Lyndon串的起点kk正在考虑是否加入当前Lyndon串的字符jjs[k]s[k]比较字典序的位置

这时候,必然被当前Lyndon串包含的子串为s[i..i+(kj)1]s[i..i+(kj)1]。暂时看不懂没关系,结合下面的执行步骤就好理解了。

在刚开始运算时,i=j=1,k=2i=j=1,k=2。接着不断将kk增加,满足一定条件时改变i,ji,j

1. s[j]=s[k]s[j]=s[k],那么j=j+1j=j+1继续比较。

2. s[j]<s[k]s[j]<s[k],那么j=ij=i。可以这样理解:只要有s[j]<s[k]s[j]<s[k],那么以kk之前(包括kk)为起点的后缀必然都小于s[i..k]s[i..k]

3. s[j]>s[k]s[j]>s[k],那么i=i+(kj)i=i+(kj)。当前的Lyndon串就确定了,为s[i..i+(kj)1]s[i..i+(kj)1],接着开始构造下一个。

在2中,可以确定的Lyndon串为s[i..k]s[i..k],其长度为kj=kikj=ki

然后尝试理解一下33。举个栗子,s=abcabcabcs=abcabcabc,根据上述规则在k3k3j=1j=1,在3<k93<k9j=k3j=k3。这样一直执行到k=9k=9,运算后j=7j=7,到了k=10k=10超出了字符串长度,则值为00,有s[j]=a>s[k]=0s[j]=a>s[k]=0,此时开始截取Lyndon串。第一次截取长度为kj=3kj=3的串s[1..3]=abcs[1..3]=abc,第二次截取s[4..6]=abcs[4..6]=abc,第三次截取s[7..9]=abcs[7..9]=abc

也就是说,3是在处理循环的分割。

模板题:洛谷P6114

复制代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N=5000005;

int n;
char s[N];

int main()
{
    scanf("%s",s+1);
    n=strlen(s+1);
    
    int ans=0;
    for(int i=1;i<=n;)
    {
        int j=i,k=i+1;
        while(k<=n && s[j]<=s[k])
            j=(s[j]==s[k++]?j+1:i);
        while(i<=j)
            i+=(k-j),ans^=(i-1);
    }
    
    printf("%d\n",ans);
    return 0;
} 
复制代码

这个算法是的时间复杂度显然是O(n)O(n)的,因为i,ki,k均单调增。

 


 

~ 一些题目 ~

 

HDU 6761  (Minimum Index,2020 Multi-University Training Contest 1)

对于一个字符串ss进行Lyndon分解后,在大多数情况下,s[1..i]s[1..i]的minimum index就等于s[1..i]s[1..i]所包含的最后一个Lyndon串的开头位置。

不过观察样例中的s=aabs=aab能够发现,若在某个时刻jiji,那就说明s[i..j]s[i..j]s[k(ji)+1..k]s[k(ji)+1..k]相等,此时minimum index等于k(ji)+1k(ji)+1。有一个很讨巧的写法,就是用jj处的minimum index加上kjkj,其中kjkj就相当于最后一个循环相对于第一个循环的位移。而j=ij=i时,则有minimum index等于ii

对于两种确定minimum index的方式分开计算就结束了。

复制代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N=1000005;

int n;
char s[N];
int mpos[N];

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%s",s+1);
        n=strlen(s+1);
        
        for(int i=1;i<=n;)
        {
            int j=i,k=i+1;
            mpos[i]=i;
            while(k<=n && s[j]<=s[k])
            {
                j=(s[j]==s[k++]?j+1:i);
                mpos[k-1]=(j==i?i:mpos[j-1]+(k-j));
            }
            while(i<=j)
                i+=(k-j);
        }
        
        int ans=0;
        for(int i=n;i>=1;i--)
            ans=(1112LL*ans+mpos[i])%1000000007;
        printf("%d\n",ans);
    }
    return 0;
}
View Code
复制代码

 

(剩余内容暂时咕咕咕)

posted @   LiuRunky  阅读(634)  评论(0编辑  收藏  举报
编辑推荐:
· .NET制作智能桌面机器人:结合BotSharp智能体框架开发语音交互
· 软件产品开发中常见的10个问题及处理方法
· .NET 原生驾驭 AI 新基建实战系列:向量数据库的应用与畅想
· 从问题排查到源码分析:ActiveMQ消费端频繁日志刷屏的秘密
· 一次Java后端服务间歇性响应慢的问题排查记录
阅读排行:
· 互联网不景气了那就玩玩嵌入式吧,用纯.NET开发并制作一个智能桌面机器人(四):结合BotSharp
· 一个基于 .NET 开源免费的异地组网和内网穿透工具
· 《HelloGitHub》第 108 期
· Windows桌面应用自动更新解决方案SharpUpdater5发布
· 我的家庭实验室服务器集群硬件清单
历史上的今天:
2019-07-23 快速线性递推
点击右上角即可分享
微信分享提示