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 分解:

\[\begin{aligned} S&=A_1 A_2\cdots A_iA_{i+1}A_{i+1}\cdots A_{m_1}\\\\ S&=A_1 A_2\cdots A_iA^{\prime}_{i+1}A^{\prime}_{i+1}\cdots A^{\prime}_{m_2} \end{aligned} \]

\(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 串的性质可得:

\[A_{i+1}<A^\prime_{k+1}[:l]\leq A^\prime_{k+1}\leq A^\prime_{i+1}<A_{i+1} \]

产生矛盾。

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;
}

求解每个前缀的最小后缀

link

一个字符串最小后缀即为其 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

[OIWIKI] Lyndon 分解

[洛谷] 题解 P6127 【【模板】Lyndon 分解】- wucstdio

[洛谷] P9719 [EC Final 2022] Minimum Suffix - ningago

[cnblogs] Lyndon 分解学习笔记 - harryzhr

[cnblogs] HDU 6761 Minimum Index(Lyndon分解) - Ldler

[洛谷] 题解 P5334 【[JSOI2019] 节日庆典】- qwaszx

posted @ 2025-01-24 22:28  Displace  阅读(114)  评论(0)    收藏  举报