【洛谷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;
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu5334.html