zoukankan      html  css  js  c++  java
  • Lyndon分解

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

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

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


    ~ 两个概念 ~

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

    Lyndon分解:将字符串$s$按顺序不重叠地分解成$s_1+...+s_m$共$m$个Lyndon串,且保证$s_igeq s_{i+1},1leq i<m$。

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

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

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


    ~ Duval算法 ~

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

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

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

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

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

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

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

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

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

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

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

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

    在2中,可以确定的Lyndon串为$s[i..k]$,其长度为$k-j=k-i$。

    然后尝试理解一下$3$。举个栗子,$s=abcabcabc$,根据上述规则在$kleq 3$时$j=1$,在$3<kleq 9$时$j=k-3$。这样一直执行到$k=9$,运算后$j=7$,到了$k=10$超出了字符串长度,则值为$0$,有$s[j]=a>s[k]=0$,此时开始截取Lyndon串。第一次截取长度为$k-j=3$的串$s[1..3]=abc$,第二次截取$s[4..6]=abc$,第三次截取$s[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
    ",ans);
        return 0;
    } 

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


    ~ 一些题目 ~

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

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

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

    对于两种确定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
    ",ans);
        }
        return 0;
    }
    View Code

    (剩余内容暂时咕咕咕)

  • 相关阅读:
    ActiveMQ (一) 简介
    MSMQ .NET下的应用
    MSMQ
    RabbitMq C# .net 教程
    Rabbit MQ
    Dynamics 365—脚本
    DNS服务器地址汇总
    特殊字符 编码
    4s前置摄像头调用
    登陆前后导航栏处理 2015-12-12
  • 原文地址:https://www.cnblogs.com/LiuRunky/p/Lyndon_Factorization.html
Copyright © 2011-2022 走看看