题意:
给定 n n n个数,表示 n n n的某个排列。现在你可以交换两个坐标差值大于等于 n / 2 n/2 n/2的数,最后使得序列从小到大有序。输出交换次数和交换的坐标。
思路:
设第
i
i
i个数所在位置为
i
d
id
id,要从
i
d
id
id复原到
i
i
i,有五种复原状态:
①
i
d
−
i
①id-i
①id−i
②
i
d
−
1
−
n
−
i
②id-1-n-i
②id−1−n−i
③
i
d
−
n
−
1
−
i
③id-n-1-i
③id−n−1−i
④
i
d
−
1
−
i
④id-1-i
④id−1−i
⑤
i
d
−
n
−
i
⑤id-n-i
⑤id−n−i
直接判断这些条件然后暴力模拟交换就可以了。
我的方法是已经确定了 1~i-1是有序的,因此后续交换过程中,如果交换了前面有序的数,一定要记着再交换回去。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e5+5;
int num[maxn],n,a[maxn];
vector<pair<int,int> >ans;
//1 2 3 4 6 7 5
void push(int x,int y){
ans.push_back(make_pair(x,y));
num[a[x]] = y;
num[a[y]] = x;
swap(a[x],a[y]);
}
int main(){
scanf("%d",&n);
for(int i = 1; i <= n; i++){
scanf("%d",&a[i]);
num[a[i]] = i;
}
int m = n/2;
for(int i = 1; i <= n; i++){
if(num[i] == i) continue;
int id = num[i];
int dif = abs(i-id);
if(dif >= m){
push(i,id);
}else{
if(i <= m){
if(id <= m){
push(n,id);
push(n,i);
}else{
push(1,id);
push(1,n);
push(i,n);
push(1,id);
}
}else{
if(id <= m){
push(n,id);
push(1,id);
push(1,i);
push(n,id);
}else{
push(1,id);
push(i,1);
}
}
}
}
// for(int i = 1; i <= n; i++) printf("%d\n",num[i]);
// //continue;
int len = ans.size();
printf("%d\n",len);
for(auto v:ans){
printf("%d %d\n",v.first,v.second);
}
}
/*3 4 1 2
1 4 3 2
1 2 3 4
3 1 2 4
3 4 2 1*/