给出一个排列,每次可以swap(p[i],p[(i+p[i])%n])
。
要求构造一种方案使得可以还原成递增顺序。
(nle 100)
次数限制(2*10^5)
%%%gmh77当场爆切。
表示自己想几个晚上都没有想出来,也看不懂别人的题解,然后直接对着代码理解。
下面尝试模拟正推的思路。
首先可以发现(1)的特殊性。如果操作(p_i=1)的位置,相当于将这个(1)右移一位。也就是(1)可以在不改变其它的相对顺序的情况下任意穿梭。
接着考虑如果一直有(p_i eq 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("
");
}
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
",cnt);
for (int i=1;i<=cnt;++i)
printf("%d
",ls[i]);
return 0;
}