lyndon 理论学习笔记
lyndon 理论
Definition 1
定义一个字符串 \(s\) 是 lyndon 串,当且仅当这个串的最小后缀就是这个串本身。
等价定义:这个串是自身的最小循环表示。
Lemma 1
如果 \(u\) 和 \(v\) 都是 lyndon 串且 \(u<v\),则 \(u+v\) 也是 lyndon 串。
证明
Case 1: \(len(u)\geq len (v)\)
这两个串在 \(len (v)\) 之前出现过 \(u\) 中小于 \(v\) 中的字符,所以显然 \(v>u+v\)。
又因为 \(u\) 是 lyndon 串,所以 \(\forall i\in[1, len(u)]\), \(u[i:]+v>u+v\)。
又因为 \(v\) 是 lyndon 串,所以 \(\forall i\in[1, len(v)]\), \(v[i:]>v>u+v\)。
Case 2: \(len(u)<len(v)\)
若 \(u\) 不是 \(v\) 的前缀,则 \(v>u+v\),证明同上。
若 \(u\) 是 \(v\) 的前缀,如果有 \(v<u+v\),则有 \(v[len(u)+1:]<v\) (即去掉前缀),与 \(v\) 是 lyndon 串矛盾。
Definition 2
定义一个字符串 \(S\) 的 lyndon 分解为一个长度为 \(m\) 的字符串序列 \(\lbrace A\rbrace\),满足:
-
\(A_1+A_2+\cdots+A_m=S\)。
-
\(\forall i\in [1, m]\),\(A_i\) 是 lyndon 串。
-
\(\forall i\in [1, m-1]\), \(A_i\geq A_{i+1}\)。
可以证明 lyndon 分解存在且唯一。
存在性
初始令 \(m=|S|,A_i=S[i]\),不断寻找 \(A_i<A_{i+1}\) 合并。
根据 Lemma 1 ,合并得到串也是 lyndon 串,最终一定会形成 \(\forall i\in [1, m-1]\), \(A_i\geq A_{i+1}\)。
唯一性
假设存在两个 lyndon 分解:
设 \(len(A_{i+1})>len(A^{\prime}_{i+1})\),即 \(A_{i+1}>A^\prime_{i+1}\)。
假设 \(A_{i+1}=A^\prime_{i+1}A^\prime_{i+2}\cdots A^{\prime}_k A^{\prime}_{k+1}[:l]\),由 lyndon 串的性质可得:
产生矛盾。
Lemma 2
若字符串 \(v\) 和字符 \(c\) 满足 \(v+c\) 是某个 lyndon 串的前缀,则任意字符 \(d>c\) 都有 \(v+d\) 是 lyndon 串。
证明
设该 lyndon 串是 \(v+c+t\)。
则 \(\forall i\in[2, |v|]\), \(v[i:]+c+t>v+c+t\),即 \(v[i:]+c>v+c>v\)。
所以 \(\forall i\in[2, |v|]\), \(v[i:]+d>v[i:]+c>v\)。
又因为 \(c>v[1]\),所以 \(d>c>v[1]\)。
所以 \(v+d\) 是 lyndon 串。
Duval's Algorithm
该算法核心在于将原串划分为三个子串 \(S=s1+s2+s3\),其中 \(s2\) 的形式为 \(t^h+t[:x], x\in[0, |t|], t \text{ is a lyndon string}\)。之后通过增量法和上述引理构造 lyndon 分解。
记录三个变量 \(i,j,k\)。将 \(i\) 指向 \(s2\) 的开头,用于标识当前处理的子问题;将 \(j\) 指向 \(s3\) 的开头,用于标识新增的字符位置;记录 \(k=j-|t|\),用于判断增量以及标记周期长度。
分三类情况:
-
当 \(s[j]=s[k]\) 时,令 \(j\leftarrow j+1, k\leftarrow k+1\),表示维持原有周期。
-
当 \(s[j]>s[k]\) 时,由 Lemma 2 得到 \(t[:x]+s[k]\) 是 lyndon 串,并且可以不断向前合并,最终 \(s2+s[k]\) 形成新的 lyndon 串并成为新的周期,令 \(j\leftarrow j+1, k\leftarrow i\)。
-
当 \(s[j]<s[k]\) 时,当前子问题的 lyndon 分解已固定,存储当前 lyndon 分解 为 \(h\) 个 \(t\),令 \(i\leftarrow i+|t^h|\),递归新的子问题。
复杂度分析
空间复杂度显然为 \(O(1)\)。
记录 lyndon 的部分为线性时间复杂度; 变量 \(i\) 单调移动,变量 \(k\) 移动量小于变量 \(j\),变量 \(j\) 移动量小于变量 \(i\),所以三个变量总移动量为 \(O(n)\)。
总体而言,时间复杂度为 \(O(n)\)。
事实上,存在构造到运算量上界的数据,运算量上界为 \(4n-3\)。
参考实现
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double dou;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mapa make_pair
typedef long double ld;
typedef unsigned long long ull;
#define ep emplace_back
template <typename T>inline void read(T &x){
x=0;char c=getchar();bool f=0;
for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c^48);
x=(f?-x:x);
}
const int N=5e6+5;
int n;
char s[N];
vector<int> pos;
int main(){
// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
scanf("%s", s+1);
n=strlen(s+1);
for(int i=1; i<=n; ){
int k=i, j=i+1;
while(j<=n){
if(s[j]==s[k]) {
++j; ++k;
}
else if(s[j]>s[k]){
k=i; ++j;
}
else{
break;
}
}
while(i<=k){
pos.ep(i+(j-k)-1);
i+=j-k;
}
}
int ans=0;
for(int t:pos) ans^=t;
printf("%d\n", ans);
return 0;
}
Lemma 3
lyndon 串没有 border。
证明显然。
求解最小表示
设字符串 \(s\) 长度为 \(n\),对 \(s+s\) 求 lyndon 分解,找到其中跨过 \(n\) 的 lyndon 串,其开头即为 \(s\) 的最小表示的开头,时间复杂度为线性。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double dou;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mapa make_pair
typedef long double ld;
typedef unsigned long long ull;
#define ep emplace_back
template <typename T>inline void read(T &x){
x=0;char c=getchar();bool f=0;
for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c^48);
x=(f?-x:x);
}
const int N=5e6+5;
int n;
int s[N];
vector<int> pos;
int main(){
// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
read(n);
for(int i=1; i<=n; ++i) read(s[i]), s[n+i]=s[i];
n<<=1;
pos.ep(0);
for(int i=1; i<=n; ){
int k=i, j=i+1;
while(j<=n){
if(s[j]==s[k]) {
++j; ++k;
}
else if(s[j]>s[k]){
k=i; ++j;
}
else{
break;
}
}
while(i<=k){
pos.ep(i+(j-k)-1);
i+=j-k;
}
}
n>>=1;
for(int i=1; i<(int)pos.size(); ++i){
if(pos[i-1]+1<=n&&pos[i]>=n){
int it=pos[i-1]+1;
while(n--) printf("%d ", s[it]), ++it;
return 0;
}
}
return 0;
}
求解每个前缀的最小后缀
一个字符串最小后缀即为其 lyndon 分解的最后一个 lyndon 串。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double dou;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mapa make_pair
typedef long double ld;
typedef unsigned long long ull;
#define ep emplace_back
template <typename T>inline void read(T &x){
x=0;char c=getchar();bool f=0;
for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c^48);
x=(f?-x:x);
}
const int N=2e7+5, mod=1e9+7;
int T, n;
char s[N];
int ans[N];
int main(){
// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
read(T);
while(T--){
scanf("%s", s+1);
n=strlen(s+1);
for(int i=1; i<=n; ){
ans[i]=i;
int k=i, j=i+1;
while(j<=n){
if(s[j]==s[k]) {
ans[j]=ans[k]+j-k;
++j; ++k;
}
else if(s[j]>s[k]){
ans[j]=i;
k=i; ++j;
}
else{
break;
}
}
while(i<=k){
i+=j-k;
}
}
int res=0; ll pw=1;
for(int i=1; i<=n; ++i) res=(res+pw*ans[i]%mod)%mod, pw=pw*1112%mod;
printf("%d\n", res);
}
return 0;
}
例题
[EC Final 2022 D] Minimum Suffix
题意
根据下面给出的信息,构造一个字典序最小的长度为 \(n\) 的字符串 \(s\),或报告无解:
定义 \(p_i:\) \(s[p_i: i]\) 是 \(s[1:i]\) 的最小后缀,给出 \(\lbrace p\rbrace\),保证 \(1\leq p_i\leq i\)。
题解
初始定义变量 \(r=n, l=p_n\),则可以通过不断 \(r^\prime=l-1, l^\prime=p_r\) 得到 \(s\) 的 lyndon 分解。
考虑 lyndon 分解每一个 lyndon 串内部,先处理子问题。
考虑模拟 Duval 算法:
-
\(s[j]<s[k]\),此时对应 lyndon 分解找到新的划分串,这是划分串之间的问题,可以暂时不考虑。
-
\(s[j]>s[k]\),此时对应找到了新的循环节,应该有 \(p_j=l\)。
-
\(s[j]=s[k]\),此时对应保持原有循环节,应该有 \(j-p_j=k-p_k\)。
如果 \(p_j\) 不满足上述任意一条,则可以报告无解。
否则我们可以在模拟 Duval 算法的过程中得到:
-
\(tlen:\) 当前循环节的长度。
-
\(pre_i:\) 如果 Duval 算法的 \(j\) 移动到 \(i\),则 \(k=pre_i\)。
-
\(add_i:\) 辅助贪心求解字典序最小的 \(s\)。具体的,如果要求 \(s_i=s_{pre_i}\),则 \(add_i\) 为 0,否则为 1。
在子问题内部可以直接从前往后扫描,即 \(s_i=s_{pre_i}+add_i\)。
但对于子问题之间的限制(也就是上述限制中的第一条),还需要考虑 lyndon 分解 \(\lbrace A\rbrace\) 的限制:\(A_{i}\geq A_{i+1}\)。
考虑从后往前贪心,如果出现当前 lyndon 串的字典序小于上一个 lyndon 串的字典序的情况,考虑找到这个位置 \(i\) 进行调整:
-
若 \(add_i=1\),则没有 \(s_i=s_{pre_i}\) 的限制,可以直接调整为上一个 lyndon 串中对应位置的字符。
-
若 \(add_i=0\),则需要找到最近的能调整的字符(即 \(add_j=1\) 的位置 \(j\))进行调整,之后重新从 \(j\) 处做子问题即可。
特别的,如果当前串恰好是上一个串的严格前缀时,也需要调整,执行第二条即可。
因为最多会调整一次,所以这个模拟 Duval 的算法的时间复杂度是线性的。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double dou;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mapa make_pair
typedef long double ld;
typedef unsigned long long ull;
#define ep emplace_back
template <typename T>inline void read(T &x){
x=0;char c=getchar();bool f=0;
for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c^48);
x=(f?-x:x);
}
const int N=6e6+5;
int T, n;
int p[N], s[N];
int pre[N], add[N];
void solve(){
read(n);
for(int i=1; i<=n; ++i) read(p[i]);
for(int i=1; i<=n; ++i) s[i]=s[n+i]=1;
int lstlen=0;
for(int r=n, l; r>=1; r=l-1){
l=p[r];
add[l]=1; s[l]=s[r+1];
for(int i=l; i<=r; ++i){
if(p[i]<l){
printf("-1\n"); return ;
}
}
int tlen=1;
for(int i=l+1; i<=r; ++i){
pre[i]=i-tlen;
if(p[i]==l) add[i]=1, tlen=i-l+1;
else if(i-p[i]==i-tlen-p[i-tlen]) add[i]=0;
else {
printf("-1\n"); return ;
}
}
bool flag=0;
for(int i=1; i<=r-l; ++i){
s[l+i]=s[pre[l+i]]+add[l+i];
if(s[l+i]>s[r+1+i]) flag=1;
if((!flag)&&s[l+i]<s[r+1+i]){
if(add[l+i]) s[l+i]=s[r+1+i];
else {
int it=i-1;
while(!add[l+it]) --it;
i=it;
++s[l+it];
flag=1;
}
}
}
if((!flag)&&r-l+1<lstlen){
int it=r-l;
while(!add[l+it]) --it;
++s[l+it];
for(int i=l+it+1; i<=r; ++i) s[i]=s[pre[i]]+add[i];
}
lstlen=r-l+1;
}
for(int i=1; i<=n; ++i) printf("%d ", s[i]);
putchar('\n');
}
int main(){
// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
read(T);
while(T--){
solve();
}
return 0;
}
[JSOI2019] 节日庆典
题意
求每个前缀的最小表示法起点。
题解
对于某个前缀,求出其 lyndon 分解后,起点显然只会在某个 lyndon 串的起点,否则字典序一定大于所在的那个 lyndon 串。
设 lyndon 分解为 \(s_1^{k_1}s_2^{k_2}\cdots s_m^{k_m}\),我们断言如果取的起点形式为 \(s_i^{t}s_{i+1}^{k_{i+1}}\cdots s_m^{k_m}\),则 \(t=k_i\)。
证明
假设 lyndon 分解为 \(a+b+b+c\),\(c\) 可以是空串,\(a>b>c\),现比较 \(b+c+a+b\) 和 \(b+b+a+c\) 的字典序,容易发现后者更小。这个结论显然可以推广到一般。
继续尝试减少比较量。
设 \(S_i=s_i^{k_i}s_{i+1}^{k_{i+1}}\cdots s_m^{k_m}\),考虑 \(S_i\) 和 \(S_{i+1}\) 的关系。
-
当 \(S_{i+1}\) 是 \(S_i\) 的前缀时,两者都有可能成为最小表示的起点。
-
当 \(S_{i+1}\) 不是 \(S_i\) 的前缀时,任意 \(x\geq i\),\(S_x\) 都比 \(S_{i+1}\) 的字典序大,都不可能成为最小表示的起点。
当找到 \(S_{i+1}\) 是 \(S_i\) 的前缀时,显然这个前缀是和 \(S_{i+1}\) 不交的,所以长度至少扩大一倍,所以比较量减少到了 \(O(\log n)\)。
求出每个前缀的 lyndon 分解的最后一个 lyndon 串时间复杂度为 \(O(n)\),每个位置可能的起点量是 \(O(\log n)\),可以使用记忆化在 \(O(n\log n)\) 的时间找到所有的 \(O(n\log n)\) 个位置,利用 \(O(n)\) 预处理的 exkmp 可以做到 \(O(1)\) 求 lcp 进行比较,总而言之时间复杂度为 \(O(n\log n)\)。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double dou;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mapa make_pair
typedef long double ld;
typedef unsigned long long ull;
#define ep emplace_back
template <typename T>inline void read(T &x){
x=0;char c=getchar();bool f=0;
for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c^48);
x=(f?-x:x);
}
const int N=2e7+5, mod1=1e9+7, mod2=998244353;
int n;
char s[N];
int nxt[N];
void exkmp(){
int p=0, k=1, l;
nxt[0]=n;
while(p+1<n&&s[p]==s[p+1]) ++p;
nxt[1]=p;
for(int i=2, j; i<n; ++i){
p=k+nxt[k]-1;
l=nxt[i-k];
if(i+l<=p) nxt[i]=l;
else{
j=max(0, p-i+1);
while(i+j<n&&s[i+j]==s[j]) ++j;
nxt[i]=j;
k=i;
}
}
}
int ans[N];
pii bs=mapa(131, 13331), hs[N], pw[N];
inline pii plu(pii x, pii y){
return mapa((x.fi+y.fi)%mod1, (x.se+y.se)%mod2);
}
inline pii sub(pii x, pii y){
return mapa((x.fi-y.fi+mod1)%mod1, (x.se-y.se+mod1)%mod2);
}
inline pii mul(pii x, pii y){
return mapa(1ll*x.fi*y.fi%mod1, 1ll*x.se*y.se%mod2);
}
inline bool eq(pii x, pii y){return x.fi==y.fi&&x.se==y.se;}
inline pii get(int l, int r){
return sub(hs[r], mul(hs[l-1], pw[r-l+1]));
}
int jump[N];
int gen(int x){
if(x==0) return 0;
if(jump[x]!=0) return jump[x];
int cur=ans[x]-1;
if(cur!=0&&eq(get(ans[cur], cur), get(ans[x], x))) return jump[x]=gen(cur);
else return jump[x]=ans[x];
}
int main(){
// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
scanf("%s", s);
n=strlen(s);
exkmp();
for(int i=n; i>=1; --i) s[i]=s[i-1], nxt[i]=nxt[i-1];
pw[0]=mapa(1, 1);
for(int i=1; i<=n; ++i) pw[i]=mul(pw[i-1], bs), hs[i]=plu(mapa(s[i], s[i]), mul(hs[i-1], bs));
for(int i=1; i<=n; ){
ans[i]=i;
int k=i, j=i+1;
while(j<=n){
if(s[j]==s[k]) {
ans[j]=ans[k]+j-k;
++j; ++k;
}
else if(s[j]>s[k]){
ans[j]=i;
k=i; ++j;
}
else{
break;
}
}
while(i<=k){
i+=j-k;
}
}
for(int i=1; i<=n; ++i) {
int res=gen(i);
int lp=res, nlp=gen(res-1);
while(nlp){
if(lp-nlp<i-lp+1) break;
if(!eq(get(nlp, nlp+i-lp), get(lp, i))) break;
lp=nlp; nlp=gen(nlp-1);
int lcp=nxt[lp+i-res+1];
if(lcp<res-lp) {
if(s[lcp+1]>s[lp+i-res+1+lcp]) {
res=lp; continue;
}
}
else{
int lcp2=nxt[lcp+1];
if(lcp+lcp2+i-res+1>=i) {
res=lp; continue;
}
else if(s[lcp+lcp2+1]>s[lcp2+1]) {
res=lp; continue;
}
}
}
printf("%d ", res);
}
return 0;
}
The 3rd Universal Cup. Stage 25: Hangzhou D
题意
将字符串 \(S\) 划分为两个子序列 \(B,C\)(可以为空),使得 \(B\leq C\),最小化 \(C\) 的字典序。
题解
Lemma 1
- 对于 lyndon 串 \(S\),不存在非空划分方案 \(A, B\),使得 \(A,B\leq S\)。
证明
假设 \(S=c+T\),不妨设 \(A=c+U\)。
当 \(U\) 为空时,\(B=T\)。因为 \(S\) 是 lyndon 串,所以 \(T>S\),矛盾。
当 \(U\) 非空时,因为 \(A\leq S\),所以 \(U<T\);并且因为 \(B\leq S\),所以 \(B<T\),那么就需要证明把 \(S\) 删除开头字符得到的 \(T\) 存在非空划分方案 \(U,V\),使得 \(U,V<T, V\leq S\)。
继续讨论,假设 \(T=d+P\),不妨设 \(U=d+X\)。
当 \(Q\) 为空时,\(B=P\)。因为 \(S\) 是 lyndon 串,所以 \(P>S\),矛盾。
当 \(Q\) 非空时,因为 \(U<T\),所以 \(X<P\);并且因为 \(V\leq S\),所以 \(V<Q\),那么就需要证明把 \(S\) 删除开头两个字符得到的 \(P\) 存在非空划分方案 \(X,Y\),使得 \(X,Y<P, Y\leq S\)。
如此形成循环论证,而 \(S\) 的长度有限,所以产生矛盾。
Lemma 2
- 设 \(S\) 进行 lyndon 分解后前两个串为 \(s, t\),则存在最优解使得 \(s\) 属于 \(C\)。
证明
根据 Lemma 1,我们不存在划分方案使得 \(B,C\) 都小于 \(s\)。那么最优解就是让 \(s\) 属于 \(C\)。
(不严谨,严谨的待更……)
Lemma 3
- 设 \(S\) 进行 lyndon 分解后前两个串均为 \(s\),则存在最优解使得这两个 \(s\) 分别属于 \(B,C\)。
证明
根据 Lemma 2,第一个 \(s\) 属于 \(C\)。
考虑第二个 \(s\),根据 Lemma 1,它不能被分成两个 \(\leq s\) 的串。如果分出来一个 \(>s\) 的串,分给 \(B\) 不合法,分给 \(C\) 不优。所以只能把整个第二个 \(s\) 分给 \(B\)。
跑出来 \(S\) 的 lyndon 分解够归纳构造即可,复杂度线性。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double dou;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mapa make_pair
typedef long double ld;
typedef unsigned long long ull;
#define ep emplace_back
template <typename T>inline void read(T &x){
x=0;char c=getchar();bool f=0;
for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c^48);
x=(f?-x:x);
}
const int N=5005;
int T, n, m;
int a[N], b[N];
vector<int> pos;
bool check(int l1, int r1, int l2, int r2){
if(r1-l1!=r2-l2) return false;
for(int i=0; i<=r1-l1; ++i) if(a[i+l1]!=a[i+l2]) return false;
return true;
}
void Duval(){
pos.clear();
pos.ep(0);
for(int i=1; i<=n; ){
int k=i, j=i+1;
while(j<=n){
if(a[j]==a[k]) {
++j; ++k;
}
else if(a[j]>a[k]){
k=i; ++j;
}
else{
break;
}
}
while(i<=k){
pos.ep(i+(j-k)-1);
i+=j-k;
}
}
}
struct node{
int l, r, cnt;
node(int _l=0, int _r=0, int _cnt=0){l=_l; r=_r; cnt=_cnt;}
};
vector<int> ans;
void opt(){
printf("%d\n", (int)ans.size());
for(auto t:ans) printf("%d ", t);
ans.clear();
putchar('\n');
}
void paint(int l, int r){for(int i=l; i<=r; ++i) ans.ep(b[a[i]]);}
void solve(){
read(n);
for(int i=1; i<=n; ++i) read(a[i]), b[i]=a[i];
sort(b+1, b+n+1);
m=unique(b+1, b+n+1)-b-1;
for(int i=1; i<=n; ++i) a[i]=lower_bound(b+1, b+m+1, a[i])-b;
Duval();
vector<node> str;
for(int l=1, r; l<(int)pos.size(); ){
r=l+1;
while(r<(int)pos.size()&&check(pos[l-1]+1, pos[l], pos[r-1]+1, pos[r])) ++r;
str.ep(pos[l-1]+1, pos[l], r-l);
l=r;
}
for(auto t:str){
if(t.cnt&1){
for(int i=0; 2*i<=t.cnt; ++i) paint(t.l, t.r);
opt();
return ;
}
else{
for(int i=1; 2*i<=t.cnt; ++i) paint(t.l, t.r);
}
}
opt();
}
int main(){
freopen("D:\\nya\\acm\\B\\test.in","r",stdin);
freopen("D:\\nya\\acm\\B\\test.out","w",stdout);
read(T);
while(T--) solve();
return 0;
}
REF
[洛谷] 题解 P6127 【【模板】Lyndon 分解】- wucstdio
[洛谷] P9719 [EC Final 2022] Minimum Suffix - ningago
[cnblogs] Lyndon 分解学习笔记 - harryzhr

浙公网安备 33010602011771号