LOJ3103. 「JSOI2019」节日庆典
给定一个字符串,对于每个前缀,求其最小循环同构串的开头(位置为第二关键字)。
\(n\le 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)=\{v\in suf(s)|\exist r,minsuf(sr)=vr\}\)。也就是说,再加入某个字符串\(r\),有可能成为最小后缀的后缀。
性质1:如果\(u,v\in Ssuf(s),|u|<|v|\),则\(u\)为\(v\)的前缀。
性质2:如果\(u,v\in 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(n\lg 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^2v\or uv<v\)。如果\(u\ge 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_m\in 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\notin Ssuf\)。
暴力地维护前缀的Lyndon word(单调栈),就可以得到\(O(n\lg n)\)的做法。
更优秀的做法:考虑Duval算法。在做Duval算法的过程中,前面的字符串分成了\(S_1S_2\),其中\(S_1\)已经分解好了,\(S_2=ww\dots wv\),其中\(v\)是\(w\)的前缀。注意到\(S_1\)不会对答案有影响,可以归纳:算法过程中,如果\(ww\dots wv\)后面接了\(c\),使得\(vc<w\)且\(vc\)不为\(w\)前缀,则要把前面这些\(w\)丢到\(S_1\)中,于是新的\(S_2=vc\)。由于\(vc<w\)且\(vc\)不为\(w\)前缀,所以\(w\notin 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("\n");
// 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;
}