洛谷 P5334 [JSOI2019]节日庆典
考虑维护当前可能成为答案的后缀,因为当前的最小后缀加上前面一段后不一定仍最小,所以对于所有前缀为最小后缀的后缀都要进行考虑,但能成为答案的后缀并不是所有这样的后缀。
设当前考虑的串为 \(s\),后缀 \(a\) 能成为候选后缀当且仅当存在字符串 \(t\),使得 \(at\) 是 \(st\) 的最小后缀。
有一个结论是:实际要考虑的候选后缀的个数为 \(O(\log n)\)。即对于候选后缀中的任意两个后缀 \(a,b\),满足 \(|a|<|b|\),则一定有 \(2|a|\leqslant |b|\)。
用反证法来证明,若存在两个后缀 \(a,b\),满足 \(|a|<|b|<2|a|\)。因为 \(a\) 为 \(b\) 的 \(\text{border}\),所以 \(b\) 存在一个长为 \(|b|-|a|<\frac{|b|}{2}\) 的周期,设其为 \(u\),那么 \(a=uv,b=u^2v\),因为 \(a\) 在候选后缀中,所以存在字符串 \(t\),使得 \(at\) 是 \(st\) 的最小后缀,得:
\[\large\begin{aligned}
at&<bt\\
uvt&<u^2vt\\
vt&<uvt=at\\
\end{aligned}
\]
因为 \(v\) 也是 \(s\) 的非空后缀,所以这与 \(at\) 是 \(st\) 的最小后缀矛盾,因此这样的 \(a,b\) 不存在。
用扩展 \(KMP\) 预处理后就可以快速比较两个后缀。
#include<bits/stdc++.h>
#define maxn 3000010
using namespace std;
template<typename T> inline void read(T &x)
{
x=0;char c=getchar();bool flag=false;
while(!isdigit(c)){if(c=='-')flag=true;c=getchar();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
if(flag)x=-x;
}
int n,l,r;
int z[maxn];
char s[maxn];
vector<int> ve;
int main()
{
scanf("%s",s+1),z[1]=n=strlen(s+1);
for(int i=2;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) l=i,r=i+z[i]-1;
}
for(int i=1;i<=n;++i)
{
vector<int> tmp;
ve.push_back(i);
for(int j=0;j<ve.size();++j)
{
int p=ve[j];
while(!tmp.empty()&&s[i]<s[tmp.back()+i-p]) tmp.pop_back();
if(tmp.empty()||(s[i]==s[tmp.back()+i-p]&&2*(i-p+1)<=i-tmp.back()+1)) tmp.push_back(p);
}
ve=tmp;
int pos=ve[0];
for(int j=1;j<ve.size();++j)
{
int p=ve[j],x=pos+i-p+1;
if(z[x]>=p-pos)
{
x=p-pos+1;
if(z[x]<pos-1&&s[z[x]+1]>s[x+z[x]]) pos=p;
}
else if(s[z[x]+1]<s[x+z[x]]) pos=p;
}
printf("%d ",pos);
}
return 0;
}