admin官方

记一次选择位置 ii 的操作为 FiF_i。这个操作等价于翻转长度为 i−1i-1 的前缀,保持第 ii 个位置不动,再翻转长度为 n−in-i 的后缀。

一个长度为 mm 的翻转,其置换奇偶性为 m(m−1)2\frac{m(m-1)}2。因此 FiF_i 的奇偶性为 (i−1)(i−2)2+(n−i)(n−i−1)2\frac{(i-1)(i-2)}2+\frac{(n-i)(n-i-1)}2。

可以推出,当且仅当 n≡2(mod4)n\equiv 2\pmod 4 时,所有操作都是偶置换。

所以若 n≡2(mod4)n\equiv2\pmod4,且所有数互不相同,那么只能完成偶置换。若存在两个相同的数,则可以交换这两个相同数在排序后对应的目标编号,从而改变目标置换的奇偶性,但最终得到的非降序列不变。

于是唯一无解的情况是:n≡2(mod4)n\equiv2\pmod4,所有 aia_i 两两不同,并且当前序列到非降序序列的置换是奇置换。

接下来先把序列转化成排列。将所有位置按照 (ai,i)(a_i,i) 排序,排序后第 jj 个元素的目标编号为 jj。这样原序列就被转化成一个排列 pp,其中 pip_i 表示当前位置 ii 上的数应该去往的目标位置。

如果序列中存在重复元素,并且需要改变奇偶性,只需要交换两个相同元素对应的目标编号即可。之后只需考虑如何把排列 pp 变成恒等排列。

当 n≢2(mod4)n\not\equiv2\pmod4,并且当前排列为奇置换时,可以先执行一次奇操作,把排列奇偶性变成偶置换。具体来说:

  • 若 n≡0(mod4)n\equiv0\pmod4,选择位置 11;
  • 若 n≡1(mod4)n\equiv1\pmod4,选择位置 22;
  • 若 n≡3(mod4)n\equiv3\pmod4,选择位置 11。

执行后重新维护当前排列即可。

下面说明如何用原操作实现三元轮换。对于三个位置 x<y<zx<y<z,连续执行 Fx,Fn−(y−x)+1,Fz−y,FzF_x,F_{n-(y-x)+1},F_{z-y},F_z,可以实现 (Ax,Ay,Az)→(Ay,Az,Ax)(A_x,A_y,A_z)\to(A_y,A_z,A_x),也就是对位置 x,y,zx,y,z 做一次三元轮换,其余位置不变。

如果反向执行这四次操作,则可以实现反方向的三元轮换,即 (Ax,Ay,Az)→(Az,Ax,Ay)(A_x,A_y,A_z)\to(A_z,A_x,A_y)。因此可以实现任意三个不同位置上的三元轮换。

接下来用三元轮换排序偶置换。维护两个数组:pip_i 表示当前位置 ii 上元素的目标位置,posxpos_x 表示目标位置为 xx 的元素当前所在位置。

从左到右扫描。如果 pi=ip_i=i,直接跳过。否则令 j=posi,x=pij=pos_i,x=p_i。若 j≠xj\ne x,则对位置 (i,j,x)(i,j,x) 做一次三元轮换,使得位置 ii 得到目标为 ii 的元素,位置 xx 得到目标为 xx 的元素。这样一次三元轮换至少可以固定两个位置。

剩下的一种情况是 pi=j,pj=ip_i=j,p_j=i,也就是一组二元环。二元环不能单独用一次三元轮换解决,先把它保存下来。

由于当前排列是偶置换,二元环数量一定为偶数。对于两组二元环 (a,b),(c,d)(a,b),(c,d),先对 (a,b,c)(a,b,c) 做一次三元轮换,再对 (b,c,d)(b,c,d) 做一次三元轮换,即可同时消去这两组二元环。

最后估计操作次数。每个三元轮换使用 44 次原操作;普通三元轮换每次至少固定两个位置,两组二元环使用两次三元轮换固定四个位置。因此三元轮换次数不超过 n2\frac n2,原操作次数不超过 2n2n。

再加上可能预先执行的一次奇操作,总操作次数不超过 2n+12n+1。由于 n≤105n\le10^5,所以 2n+1≤2×105+12n+1\le 2\times10^5+1,满足题目限制。

排序需要 O(nlog⁡n)O(n\log n) 的时间,构造操作需要 O(n)O(n) 的时间,因此总时间复杂度为 O(nlog⁡n)O(n\log n),空间复杂度为 O(n)O(n)。


本题的部分分主要按照 nn 的大小逐步放开。较小的数据用于鼓励选手直接搜索状态图,或者用 BFS / DFS 判断目标序列是否可达,从而观察到操作背后的排列性质。随着 nn 增大,暴力搜索不可行,选手需要转向研究操作对应的置换,并发现 n mod 4n\bmod 4 与置换奇偶性的关系。最后的满分数据要求选手不仅能判断是否有解,还要构造出不超过限制次数的操作序列,因此需要掌握用题目操作模拟三元轮换的构造方法。

xiaoliebao1115

神秘随机化做法

给出一种可能并没有正确性的做法,但是的确可以通过。

我懒得想怎么 hack 了。

有会 hack 的可以尝试。

考虑先离散化,注意,相等的数可以随意钦定成不同的数,其实这一步和逆序对奇偶性有关。

这样离散化成一个排列。

对于一个数 ii 从 11 到 nn,如果它的位置不在 nn,我就先操作 ii,然后再操作 i+1i+1。这样一直进行下去会在左边形成一个下降段。当碰到 ii 的位置在 nn 的时候,直接操作 nn,现在的情况就相当于原来的 reverse,后面操作同理。

经过上述操作有可能变成递增或递减序列。

如果是递减序列,我们在开始时随机扰动一下然后在做即可。

上述操作直接做是平方的,但是注意到每次操作其实就是两个循环移位,而当前 ii 已经没用了可以抛弃,于是可以直接让最后一个数占据 ii 的位置,手画一下这个是正确的。容易 O(n)O(n) 维护。

#include<bits/stdc++.h>
using namespace std;
int t;
const int nn=1e5+5;
int n,bf[nn],a[nn];
unsigned int mod=1e9;
mt19937 rnd(time(0));
int f(){
	unsigned int sb=rnd()%mod+1;
	return sb;
}
#define pb push_back
vector<int> res;
int p[nn],pos[nn];
bool cmp(int l1,int l2){
	return bf[l1]<bf[l2];
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>t;
	while(t--){
		cin>>n;
		for(int i=1;i<=n;i++) cin>>bf[i],p[i]=i;
		sort(p+1,p+n+1,cmp);
		int wz=0;
		for(int i=1;i<n;i++){
			if(bf[p[i]]==bf[p[i+1]]){
				wz=i;
				break;
			}
		}
		for(int tt=1;tt<=30;tt++){
			int x=f()%n+1;
			if(wz) swap(p[wz],p[wz+1]);
			for(int i=1;i<=n;i++) a[p[i]]=i;
			res.clear();
			if(tt>1){
				res.pb(x);
				if(x-1>=1) reverse(a+1,a+x);
				if(x+1<=n) reverse(a+x+1,a+n+1);
			}
			if(tt>20){
				x=f()%n+1;
				res.pb(x);
				if(x-1>=1) reverse(a+1,a+x);
				if(x+1<=n) reverse(a+x+1,a+n+1);
			}
			for(int i=1;i<=n;i++) pos[a[i]]=i;
			int fl=0;
			for(int i=1;i<=n;i++){
				int r=n-i+1;
				if(pos[i]==r){
					if(fl==0) res.pb(n);
					else res.pb(1);
					fl^=1;
				}
				else{
					if(fl==0) res.pb(pos[i]+i-1),res.pb(pos[i]+i);
					else res.pb(n-pos[i]-(i-1)+1),res.pb(n-pos[i]-(i-1));
					pos[a[r]]=pos[i];
					a[pos[i]]=a[r];
				}
			}
			if(fl){
				cout<<res.size()<<"\n";
				for(int v:res) cout<<v<<" ";
				cout<<"\n";
				break;
			}
			if(tt==30) cout<<-1<<"\n";
		}
	}
	return 0;
}