给定一个字符串,对于每个前缀,求其最小循环同构串的开头(位置为第二关键字)。
(nle 3*10^6)
用到了一点Significant Suffixes的知识。参考博客:https://www.luogu.com.cn/blog/command-block/border-li-lun-xiao-ji
Significant Suffixes,最小后缀。
令(minsuf(s))表示(s)的最小后缀,
令(Ssuf(s)={vin suf(s)|exist r,minsuf(sr)=vr})。也就是说,再加入某个字符串(r),有可能成为最小后缀的后缀。
性质1:如果(u,vin Ssuf(s),|u|<|v|),则(u)为(v)的前缀。
性质2:如果(u,vin Ssuf(s),|u|<|v|),则(2|u|<|v|)。
证明:如果(|u|<|v|le 2|u|),设(u=AB,v=AAB)。
如果存在后缀(R),使(ABR<AABR),则(BR<ABR)。由于(B)也是一个后缀,它不是最小后缀。矛盾。
推论:(|Ssuf(s)|=O(lg n))。
那么可以对每个前缀维护(Ssuf)。
每次在集合中加入一个字符(c)。然后对于相邻的字符串(s_i,s_{i+1})(设(|s_i|>|s_{i+1}|)):
- 如果(|s_{i+1}|)不是(|s_i|)的前缀,则比较大小,保留小的那个。
- 否则,如果(|s_i|le 2|s_{i+1}|),则删去(s_{i+1})。
维护集合,枚举比较即可。
因为比较时一个是另一个的前缀,所以可以据此优化一下比较方式。用exkmp预处理一下,就可以(O(1))出来了。
时间(O(nlg n))。
另一种高级的Lyndon word做法:参考博客https://www.luogu.com.cn/blog/qwaszx/solution-p5334
(关于这个做法本人还有很多搞不懂的地方,如果有大佬路过请求指导)
假设对某个前缀求其答案,设这个前缀Lyndon分解之后长成(w_1^{k_1}w_2^{k_2}dots w_{m}^{k_m})。
显然选择的最小循环串开头是某个Lyndon串的开头,可以表示成:(w_i^kw_{i+1}^{k_{i+1}}dots w_{m}^{k_m})。
结论1:此时(k=k_i)。
证明:即证(uv<u^2vor uv<v)。如果(uge v),则有(u^2v>uv>v);否则:如果(u)不是(v)的前缀,则有(uv<v);如果(u)是(v)的前缀,把前缀公共部分截掉,剩下是个子问题递归证明。
这个结论对于任意字符串适用。
结论2:(u,v)是任意串,(w)是Lyndon word,如果(u<w,v<w),则(uv<w)。
设(S_i=w_i^{k_i}dots w_m^{k_m})。于是有(w_i^{k_i}ge w_i>S_{i+1})。
显然(S_min Ssuf),如果(S_{m-1}in Ssuf),则(2|S_m|>|S_{m-1}|),此时有(S_m)是(w_{m-1}^{k_{m-1}})的前缀;再往前推,直到找到第一个(j),满足(S_{j+1})不是(w_{j}^{k_j})的前缀,那么(S_{j+1},dots,S_{m}in Ssuf),并且(S_1,S_2,dots,S_j otin Ssuf)。
暴力地维护前缀的Lyndon word(单调栈),就可以得到(O(nlg n))的做法。
更优秀的做法:考虑Duval算法。在做Duval算法的过程中,前面的字符串分成了(S_1S_2),其中(S_1)已经分解好了,(S_2=wwdots wv),其中(v)是(w)的前缀。注意到(S_1)不会对答案有影响,可以归纳:算法过程中,如果(wwdots wv)后面接了(c),使得(vc<w)且(vc)不为(w)前缀,则要把前面这些(w)丢到(S_1)中,于是新的(S_2=vc)。由于(vc<w)且(vc)不为(w)前缀,所以(w otin Ssuf)。于是对答案有影响的只有:第一个(w)的开头,以及(v)中的每个位置。
记(a_i)表示前缀(i)的最小循环串的开头。设做Lyndon分解的三个指针为(i,j,k)(定义见Lyndon分解学习小记)。
(i)刚刚被重置时,令(a_i=i)。
当(s_k=s_j):找到(k)在第一个(w)中对应的位置,设为(t)((t=i+(j-i)mod (k-j)))。令(a_k)为(k-(t-a_t))(即从第一个(w)中的位置对应到(v)中的位置)和(i)中的最优解。(如果(a_t<i)则不优,不算)
当(s_k>s_j):令(a_k=i)。
其余的部分和普通的算法流程一样。注意对(a_k)((a_i))操作时,都有(a_k)没有被操作过作为前提。
正确性:(s[1,k])中,开头在(v)中的循环串,等于(s[1,t])中,开头在(w[1,|v|])的循环串末尾加若干个(u),其中(u)是(w)的一个循环同构串。(a_t)是(s[1,t])的答案,如果没有相等的循环串,那么对应过来也最优;至于如果有相等的循环串的情况,局限于目前的水平我并没有分析出来。另外为什么要以(a_k)没有被操作过为前提,我也不清楚。
同样也需要预处理exkmp来帮助比大小。时间(O(n))。
using namespace std;
#include <bits/stdc++.h>
#define N 3000005
int n;
void print(int x){
static int st[20];
int tp=0;
if (x==0)
putchar('0');
else{
while (x)
st[++tp]=x%10,x/=10;
while (tp)
putchar(st[tp--]+'0');
}
}
char s[N];
int nxt[N];
void exkmp(){
int pos=0,mr=0;
nxt[0]=n;
for (int i=1;i<n;++i){
if (i<=mr)
nxt[i]=min(nxt[i-pos],mr-i);
while (i+nxt[i]<n && s[i+nxt[i]]==s[nxt[i]])
nxt[i]++;
if (i+nxt[i]>mr)
mr=i+nxt[i],pos=i;
}
}
int st[50],tp;
bool cmp(int u,int v,int l){
u+=l-v+1,v=0;
if (u+nxt[u]-1<l)
return s[u+nxt[u]]<=s[nxt[u]];
v+=l-u+1,u=0;
if (v+nxt[v]-1<l)
return s[nxt[v]]<=s[v+nxt[v]];
return 1;
}
int main(){
freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
scanf("%s",s);
n=strlen(s);
exkmp();
for (int i=0;i<n;++i){
st[++tp]=i;
st[tp+1]=-1;//pay attention
for (int j=tp;j>=2;--j){
if (s[i]<s[st[j-1]+i-st[j]]){
st[j-1]=st[j];
st[j]=-1;
}
else if (s[i]>s[st[j-1]+i-st[j]] || 2*(i-st[j]+1)>=(i-st[j-1]+1)){//the order of "if"
st[j]=st[j+1];
st[j+1]=-1;
}
}
int tmp=tp;
tp=0;
for (int j=1;j<=tmp;++j)
if (st[j]!=-1)
st[++tp]=st[j];
// for (int j=1;j<=tp;++j)
// printf("%d ",st[j]);
// printf("
");
// continue;
int ans=st[tp];
for (int j=tp-1;j>=1;--j)
if (cmp(st[j],ans,i))
ans=st[j];
print(ans+1),putchar(' ');
}
return 0;
}
using namespace std;
#include <bits/stdc++.h>
#define N 3000005
int n;
void print(int x){
static int st[20];
int tp=0;
if (x==0)
putchar('0');
else{
while (x)
st[++tp]=x%10,x/=10;
while (tp)
putchar(st[tp--]+'0');
}
}
char s[N];
int nxt[N];
void exkmp(){
int pos=0,mr=0;
nxt[0]=n;
for (int i=1;i<n;++i){
if (i<=mr)
nxt[i]=min(nxt[i-pos],mr-i);
while (i+nxt[i]<n && s[i+nxt[i]]==s[nxt[i]])
nxt[i]++;
if (i+nxt[i]>mr)
mr=i+nxt[i],pos=i;
}
}
bool cmp(int u,int v,int l){
u+=l-v+1,v=0;
if (u+nxt[u]-1<l)
return s[u+nxt[u]]<=s[nxt[u]];
v+=l-u+1,u=0;
if (v+nxt[v]-1<l)
return s[nxt[v]]<=s[v+nxt[v]];
return 1;
}
int a[N];
int main(){
freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
scanf("%s",s);
n=strlen(s);
exkmp();
int i=0;
while (i<n){
int k=i+1,j=i;
if (!a[i])
a[i]=i;
while (k<n && s[k]>=s[j]){
if (s[k]==s[j]){
int t=i+(j-i)%(k-j);
if (!a[k]){
if (a[t]<i)
a[k]=i;
else
a[k]=(cmp(i,k-(t-a[t]),k)?i:k-(t-a[t]));
}
++j,++k;
}
else{
if (!a[k])
a[k]=i;
j=i,++k;
}
}
while (i<=j)
i+=k-j;
}
for (int i=0;i<n;++i)
print(a[i]+1),putchar(' ');
return 0;
}