题意即为求每个前缀的最小循环表示。
考虑维护当前可能成为答案的后缀,因为当前的最小后缀加上前面一段后不一定仍最小,所以对于所有前缀为最小后缀的后缀都要进行考虑。
但是这样候选后缀会有很多,复杂度无法接受。发现对于两个后缀 (a,b),若 (|a|<|b|<2|a|),则 (a) 一定不优,这样候选后缀就为 (O(log n)) 个了。
用扩展 (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;
}