zoukankan      html  css  js  c++  java
  • 【洛谷5334】[JSOI2019] 节日庆典(ExKMP+Lyndon分解)

    点此看题面

    • 给定一个字符串,求每个前缀的最小表示。
    • (nle3 imes10^6)

    (Lyndon)分解

    假设我们能对每个前缀做一遍(Lyndon)分解,那么根据其定义及性质,答案就应该是最后一个(Lyndon Word)的起始位置。

    注意不能认为每个前缀的答案是原串做(Lyndon)分解后它所在(Lyndon Word)的开头,因为我们的(Duval)算法维护着(u^tv),在做整个串的时候默认在末尾位置添上了一个(-infty)所以能够保证分解彻底,而在做到某个前缀的时候(v)尚未完全分解,不能直接当作答案。

    尽管如此,此题的正解依旧需要用到(Duval)算法,仍然假设我们当前维护的是(u^tv),根据(s_k)(当前字符)与(s_j)(在先前循环节中对应的字符4)的大小分类讨论。

    如果(s_j=s_k),我们有两种选择——(u^tv)起始位置(i)或先前循环节中(k)的对应位置(j)的答案向后移一个循环节的对应位置(ans_j+k-j)

    如果(s_j<s_k),根据(Lyndon)串的性质,(u^tv)加上(s_k)后恰好成为一个完整的(Lyndon Word),此时不需要担心分解不彻底,(ans_k)就是(i)

    如果(s_j>s_k),原本的(Lyndon)串就固定了下来,与当前位置没有关系,因此等到之后再求解。

    (ExKMP)算法

    在前面的做法中又产生了一个新的问题,即对于前缀(k),如何比较两个答案(i)(ans_j+k-j)谁更优。

    方便起见记(x=i,y=ans_j+k-j,R=k),那么就是要比较(s[x:R]+s[1:x))(s[y:R]+s[1:y))的字典序。

    首先由于我们只会在(s_j=s_k)时尝试比较,(s[x:x+R-y])(s[y:R])必然相同。

    因此我们可以把比较分成两部分:先比较(s[x:R])剩余的部分(s[x+R-y+1:R])(s[1:y))的开头一部分(s[1:y-x]),然后比较(s[1:x))(s[1:y))的剩余部分(s[y-x+1:y))

    这样一来每次比较都是将一个子串([a:b])与原串的前缀([1:b-a+1])比较,这只要求出后缀(a)与原串的(lcp)长度判断在比较范围内是否存在差异,然后比较第一个不同位即可。

    要求一个后缀和原串的(lcp)长度,就是要求(Z)函数,直接上(ExKMP)就结束了。

    代码:(O(n))

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 3000000
    using namespace std;
    int n,ans[N+5];char s[N+5];
    int Z[N+5];I void ExKMP()//预处理Z函数
    {
    	RI i,id,Mx=0;for(Z[1]=n,i=2;i<=n;i+Z[i]-1>Mx&&(Mx=i+Z[id=i]-1),++i)
    		{i<=Mx&&(Z[i]=min(Z[i-id+1],Mx-i+1));W(i+Z[i]<=n&&s[i+Z[i]]==s[1+Z[i]]) ++Z[i];}
    }
    I int Mn(CI x,CI y,CI R)//比较对于前缀R,x和y哪个更优
    {
    	if(x==y) return x;RI z=Z[x+R-y+1];if(z<y-x) return s[x+R-y+1+z]<s[1+z]?x:y;//比较s[x:R]剩余部分与s[1:y)开头部分
    	if((z=Z[y-x+1])<x-1) return s[1+z]<s[y-x+1+z]?x:y;return x;//比较s[1:x)开头部分与s[1:y)剩余部分
    }
    int main()
    {
    	RI i,j,k;scanf("%s",s+1),n=strlen(s+1),ExKMP();
    	for(i=1;i<=n;i+=(k-i)/(k-j)*(k-j)) {!ans[i]&&(ans[i]=i),j=i,k=i+1;//起始位置ans[i]=i
    		W(k<=n&&s[j]<=s[k]) s[j]<s[k]?(!ans[k]&&(ans[k]=i),j=i):(!ans[k]&&(ans[k]=ans[j]<i?i:Mn(i,ans[j]+k-j,k)),++j),++k;}//s[j]<s[k]直接ans[k]=i;s[j]=s[k]比较i和ans[j]+k-j谁更优
    	for(i=1;i<=n;++i) printf("%d ",ans[i]);return 0;
    }
    
    败得义无反顾,弱得一无是处
  • 相关阅读:
    什么是继承?
    程序员兄弟们,我们的基本素质怎么样?
    C#基础概念二十五问
    windows mobile 5.0 PocketPC模拟器上网的设置
    数据库设计中的14个技巧
    Microsoft SQL Server 2005 存储过程翻页
    在.Net如何制作自定义的快捷方式(转)
    ActiveSync 没有DMA端口问题的解决方法
    原型模式(Prototype Pattern)
    获取鼠标和键盘长时间不动的时间
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu5334.html
Copyright © 2011-2022 走看看