P5334 [JSOI2019]节日庆典 扩展KMP
题意:
分析:
- 暴力:
对于每一个前缀跑一遍最小表示法,复杂度 \(O(n^2)\),期望得分 \(30pts\) ~ \(50pts\)
我原本以为这个东西可以用lyndon分解补字符做到\(O(n)\),但是lyndon分解求最小表示法要把原串倍增一下,这样每次相当于是插入两个字符,做不了/kk
- 正解:
我们维护一个集合 \(point\) 记录那些能够作为最小表示法起点的位置,即是lyndon串的后缀的起点,可以发现一个点只会加入集合一次,也就是说一个点一旦无法成为最小表示法的起点,那么它就再也无法加入集合
假设当前枚举到字符 \(s[i]\) ,我们考虑 \(point\) 集合里的元素满足什么关系:
-
对于 \(\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\) 的后缀的前缀,一旦它合法,等价于它的前缀都合法,即剩余元素都是合法的
-
对于 \(\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;
}