题意:
分析:
- 暴力:
对于每一个前缀跑一遍最小表示法,复杂度 (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-yge 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(nlog 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;
}