给出一个排列,每次可以swap(p[i],p[(i+p[i])%n])

要求构造一种方案使得可以还原成递增顺序。

\(n\le 100\)

次数限制\(2*10^5\)


%%%gmh77当场爆切。

表示自己想几个晚上都没有想出来,也看不懂别人的题解,然后直接对着代码理解。

下面尝试模拟正推的思路。

首先可以发现\(1\)的特殊性。如果操作\(p_i=1\)的位置,相当于将这个\(1\)右移一位。也就是\(1\)可以在不改变其它的相对顺序的情况下任意穿梭。

接着考虑如果一直有\(p_i\neq 0\),一直操作\(i\),到\(p_i\)还原为止,\(p_i\)上出现了所有数。

如果出现了\(0\)就立即假了,为了排除它的影响,尝试一下将它丢到\(p_{n-1}\)

对于\([1,n-1]\)的所有数,从大到小,对于\(x\)将它放到\(n-1-x\)的位置。每次一直操作\(n-1-x\)直到达到要求。这样安排的时候\(p_{n-1-x}+(n-1-x)<n-1\)始终满足,又由于从大到小确定\(x\)不能在\(n-1-x\)前面,所以可以构造出来。

于是得到了个倒序的序列。尝试搞成正序序列:因为\(p_x=n-1-x\),操作一次之后数字\(n-1-x\)移动到\(p_{n-1}\)。于是构造在操作\(x\)的时候,\(x\)从大往小搞,保证\([x+1,n-2]\)已经是一个升序序列。如此搞:每次将\(1\)移动到\(p_{n-1}\),操作一次\(x\)

搞完这些之后再操作一次\(p_0\)即可。


using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 105
int n;
int p[N],re[N];
int ls[1000000],cnt;
void opt(int i){
	ls[++cnt]=i;
	swap(re[p[i]],re[p[(i+p[i])%n]]);
	swap(p[i],p[(i+p[i])%n]);
//	for (int k=0;k<n;++k)
//		printf("%d ",p[k]);
//	printf("\n");
}
int main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	scanf("%d",&n);
	for (int i=0;i<n;++i)
		scanf("%d",&p[i]),re[p[i]]=i;
	while (re[0]!=n-1)
		opt(re[1]);
	for (int i=n-1;i>=1;--i)
		while (re[i]!=n-1-i)
			opt(n-1-i);
	for (int i=2;i<n;++i){
		while (re[1]!=n-1)
			opt(re[1]);
		opt(re[i]);
	}
	opt(0);
	printf("%d\n",cnt);
	for (int i=1;i<=cnt;++i)
		printf("%d\n",ls[i]);
	return 0;
}