P5334 [JSOI2019]节日庆典 扩展KMP

题意:

戳这里

分析:

  • 暴力:

对于每一个前缀跑一遍最小表示法,复杂度 \(O(n^2)\),期望得分 \(30pts\) ~ \(50pts\)

我原本以为这个东西可以用lyndon分解补字符做到\(O(n)\),但是lyndon分解求最小表示法要把原串倍增一下,这样每次相当于是插入两个字符,做不了/kk

  • 正解:

我们维护一个集合 \(point\) 记录那些能够作为最小表示法起点的位置,即是lyndon串的后缀的起点,可以发现一个点只会加入集合一次,也就是说一个点一旦无法成为最小表示法的起点,那么它就再也无法加入集合

假设当前枚举到字符 \(s[i]\) ,我们考虑 \(point\) 集合里的元素满足什么关系:

  1. 对于 \(\forall x<y\) 如果 \(lcp(s[x:n],s[y:n])\le i-y\) ,那么 \(x,y\) 中必然有一个节点不合法

    证明很显然,我们考虑新加入字符 \(s[i]\) 时,对于原有集合 \(point\) 集合内元素一定满足 \(\forall x,y\) \(lcp(s[x:n],s[y:n])> i-1-y\) 而我们要给每个不合法元素 \(x\) 找到一个元素 \(y\) 满足 \(lcp(s[x:n],s[y:n])\le i-y\)

    显然我们可以直接比较 \(s[x+i-y]\)\(s[i]\) 的关系,但是枚举点对是 \(O(n^2)\) 的,对于每个前缀要是都这么干一次,还不如暴力,所以我们考虑对于每一个 \(y\) 检验哪些 \(x\) 不合法时,只需要找到最小的一个 \(x\) 就可以了,因为集合内其他元素一定是 \(x\) 的后缀的前缀,一旦它合法,等价于它的前缀都合法,即剩余元素都是合法的

  2. 对于 \(\forall x<y\) 满足 \(lcp(s[x:n],s[y:n])> i-y\) ,如果 \(i-y\ge y-x\)那么 \(y\) 必然不合法

    证明:假设原串可以表示为:ABBC ,其中 C 是 B 的严格非空前缀,那么 \(x,y,z\) 分别代表的循环同构串是 BBCA,BCAB,CABB,如果 BC > CA 那么 \(y\) 不如 \(z\) 优秀,如果 BC < CA 那么 \(y\) 不如 \(x\) 优秀,所以可以直接放弃 \(y\) ,同时我们可以发现,任意两个元素之间的长度大于 2 倍,所以集合内元素的个数是 \(O(\log)\)

这样我们就能做到 \(O(\log)\) 的在加入一个字符时维护最优决策点的集合,然后我们每一次遍历集合,把前缀接上进行比较,找字典序更小的一个决策点作为答案,由于需要比较每一个后缀和原串的字典序,所以上EXKMP,总复杂度 \(O(n\log n)\)

代码:

#include<bits/stdc++.h>
#define inl inline
#define reg register
#define pb push_back

using namespace std;

namespace zzc
{
    const int maxn = 3e6+5;
    int z[maxn];
    int n;
    char s[maxn];
    vector<int> point;

    void exkmp()
    {
        z[1]=n;
        for(reg int i=2,l=0,r=0;i<=n;i++)
        {
            if(i<=r) 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) r=i+z[i]-1,l=i;
        }
    }
    
    inl int compare(int x,int len)
    {
        return z[x]>=len?0:(s[x+z[x]]>s[1+z[x]]?-1:1);
    }

    inl int check(int x,int y,int len)
    {
        int flag;
        if((flag=compare(y+len-x+1,x-y))!=0) return flag>0?y:x;
        if((flag=compare(x-y+1,y-1))!=0) return flag>0?x:y;
        return y;
    }

    void work()
    {
        scanf("%s",s+1);n=strlen(s+1);
        exkmp();
        for(reg int i=1;i<=n;i++)
        {
            vector<int> newpoint(1,i);
            for(auto x:point)
            {
                while(!newpoint.empty()&&s[x+i-newpoint.back()]<s[i]) newpoint.pop_back();//性质1
                if(newpoint.empty()||s[x+i-newpoint.back()]==s[i])
                {
                    while(!newpoint.empty()&&i-newpoint.back()>=newpoint.back()-x) newpoint.pop_back();//性质2
                    newpoint.pb(x);
                }
            }
            point=newpoint;
            int ans=point[0];
            for(reg int j=1,k=point.size();j<k;j++) ans=check(ans,point[j],i);
            printf("%d ",ans);
        }
    }

}

int main()
{
    zzc::work();
    return 0;
}
posted @ 2021-02-20 10:07  youth518  阅读(49)  评论(0编辑  收藏  举报