把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷5334】[JSOI2019] 节日庆典(ExKMP+Lyndon分解)

点此看题面

  • 给定一个字符串,求每个前缀的最小表示。
  • \(n\le3\times10^6\)

\(Lyndon\)分解

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

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

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

如果\(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;
}
posted @ 2021-05-17 15:32  TheLostWeak  阅读(138)  评论(0编辑  收藏  举报