$S$ 是Lyndon串当且仅当 $S$ 本身是所有后缀中的最小串。$S$ 是Lyndon串可以推出 $S$ 是所有循环表示中字典序最小的。
各种性质与推论:
若Lyndon串 $S=A+c+B$ ,则 $A+d$ 是Lyndon串(其中 $c,d$ 为字符,且 $d>c$ )。
证明:$suf(A)+c+B\ge A+c+B,suf(A)+c\ge A,suf(A)+d> A,d>c\ge A[1]\Rightarrow suf(A)+d\ge A+d$ 。
Lyndon分解的暴力划分:刚开始 $A_i=S_i$ ,如果 $A_i<A_{i+1}$ 则合并 $A_iA_{i+1}$ 。
Duval算法可以进行Lyndon分解。
维护三个位置 $i,j,k$ ,$S[1,i-1]$ 已经分解完毕。现在 $S[i,k-1]=A^{t}+B$ ,其中 $A$ 是Lyndon串,$A^t$ 是多个 $A$ 接一起。
$B$ 是 $A$ 的可空前缀,$j=k-|A|$ 表示 $S_k$ 在 $A$ 中的对应位置,接下来考虑加上 $k$ 这个位置:
for (int i=1;i<=n;){
int j=i,k=j+1;
for (;k<=n && s[k]>=s[j];k++) s[k]==s[j]?j++:j=i;
for (;i<=j;i+=k-j) L[++m]=i,R[m]=i+(k-j)-1;
}
由于 $|B|<|A|$ ,因此 $k$ 的移动距离最多为 $2\Delta i$ ,$i$ 的移动距离为 $\Delta i$ ,由于 $i$ 只增不减因此复杂度 $O(n)$ 且常数较小。
注意到Duval算法其实是不断地枚举周期,下面会用到这个性质。
模板题,直接用上面代码即可。
把串再接一遍,最后一个 $L_i\le n$ 的Lyndon串就是最小的循环串。
因为 $A_i\ge A_{i+1}$ ,所以显然最后一个在前半部分出现的Lyndon串就是最小的。
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=600000;
int n,pos,s[maxn+5];
int main(){
scanf("%d",&n);
for (int i=1;i<=n;i++) scanf("%d",&s[i]),s[i+n]=s[i];
n<<=1;
for (int i=1;i<=n;){
int j=i,k=j+1;
for (;k<=n && s[k]>=s[j];k++) s[k]==s[j]?j++:j=i;
for (;i<=j;i+=k-j) if (i<=(n>>1)) pos=i;
}
for (int i=pos;i<=pos+(n>>1)-1;i++)
printf("%d",s[i]),i<pos+(n>>1)-1?putchar(' '):puts(" ");
return 0;
}
这样求出的是最大位置,如果要最小位置,对于每次的循环串只取第一个 $i$ 即可。
求每个前缀的最小后缀。
对于一个前缀,Lyndon分解之后,显然最小后缀就是最后一个Lyndon串。因为如果最小后缀能往前推,则显然不满足 $A_i\ge A_{i+1}$ 。
观察算法过程,其实 $k$ 移动的过程包含着前缀的Lyndon分解过程:
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long LL;
const int maxn=1000000,MOD=1e9+7;
int te,n,ans[maxn+5];char s[maxn+5];
inline int ADD(int x,int y) {return x+y>=MOD?x+y-MOD:x+y;}
inline int MUL(int x,int y) {return (LL)x*y%MOD;}
int main(){
for (scanf("%d",&te);te;te--){
scanf("%s",s+1);n=strlen(s+1);
for (int i=1;i<=n;){
int j=i,k=j+1;
ans[i]=i;
for (;k<=n && s[k]>=s[j];k++)
if (s[k]==s[j]) ans[k]=ans[j]+k-j,j++;
else ans[k]=i,j=i;
for (;i<=j;i+=k-j);
}
int ha=0;
for (int i=1,pw=1;i<=n;i++,pw=MUL(pw,1112)) ha=ADD(ha,MUL(pw,ans[i]));
printf("%d\n",ha);
}
return 0;
}
和上面题相反,要求每个前缀的最大后缀。
最大后缀不是简单的将字符集反转就可以得出的。因为在 $A$ 是 $B$ 前缀时,$A<B$ ,但字符集翻转之后还是 $A'<B'$ ,因此是错的。
但是不难发现,字符集翻转后对于前缀进行Lyndon分解,如果最大后缀不是最后一个Lyndon串,则一定是将这个Lyndon串作为前缀的串(因为按照前面的说明,只有 $A$ 作为 $B$ 前缀时,字符集翻转后大小关系出现错误)。
所以我们还是可以考虑用周期的性质和(字符集翻转后的)Lyndon分解来求出最大后缀:
不作废是因为多出来的一段是 $A$ 的前缀,处理过程一致,则后来划分的Lyndon串肯定是之前答案的前缀,因此不会有之前的答案优秀。
注意,上面 $ans$ 均表示在 $i$ 开头考虑时的答案,而不是最终答案。所以不作废的意思就是在最终答案没有的情况下才更新 $i$ 开头的答案。
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=1000000;
int n,ans[maxn+5];char s[maxn+5];
int main(){
scanf("%s",s+1);n=strlen(s+1);
for (int i=1;i<=n;){
int j=i,k=j+1;
if (!ans[i]) ans[i]=i;
for (;k<=n && s[k]<=s[j];k++){
if (!ans[k]) ans[k]=i;
s[k]==s[j]?j++:j=i;
}
for (;i<=j;i+=k-j);
}
for (int i=1;i<=n;i++) printf("%d %d\n",ans[i],i);
return 0;
}