题意
给定一组数(1-n这n个数全排列中的一种),通过最少轮交换使序列变为升序。
每一轮可以选择任意组数对,两组数对之间不允许有相同的,在这轮中会将这些位置上的数两两交换。
题解
每个数应该在的位置是确定的,因此考虑每个位置上目前的数可能有三种情况:
- 已经在应该的位置上,无需交换
- 不在应该的位置上,但与另一个数交换位置后,两数都到了应该的位置上,即下图的一个小有向环,这样的两个数在第一轮交换就可以全部归位
- 不在应该的位置上,但可以与其他的数构成一个有向的环
这样的大环如果是点数为n的偶环,可以在第一轮通过交换拆成n/2个第二种情况那种形式的小环,因此共需两轮即可全部归位
如果是n点奇环,则可以在第一轮中通过交换拆成n/2个小环和一个已被归位的点(下图中左下角的点),因此也共需两轮交换即可归位
本题的序列在化简之后可以证明只有三种情况:
- 序列本身就为升序,不需要交换
- 序列中不存在大环,只有小环,一轮交换搞定
- 序列中存在大环,第一轮拆解大环并归位可能存在的小环,第二轮将产生的小环全部归位
所以最多只需要两轮就可以。
代码
#include <bits/stdc++.h>
using namespace std;
#define N 100005
int a[N],v[N],t[N],n,f=0;
class b{
public:
int x,y;
};
b ans[N];
bool ck(int n){//检查是否有序
for(int i=1;i<=n;i++)if(a[i]!=i)return 0;
return 1;
}
void pr(){//归位小环
for(int i=1;i<=n;i++)if(a[i]!=i&&a[a[i]]==i)ans[++f].x=i,ans[f].y=a[i],a[a[i]]=a[i],a[i]=i;
}
void out(){
printf("%d",f);
for(int i=1;i<=f;i++)printf(" %d %d",ans[i].x,ans[i].y);
printf("\n");
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
if(ck(n)==1){//本身有序
printf("0\n");return 0;
}
pr();//归位小环
if(ck(n)==1){
printf("1\n");//只有小环,一轮交换即可
out();
return 0;
}
printf("2\n");//存在大环,需两轮交换
for(int i=1;i<=n;i++){//拆解大环
if(a[i]==i||a[a[i]]==i||v[i]==1);
else{
memset(t,0,sizeof(t));
int j=a[i],l=0;v[i]=1;
while(a[j]!=a[i]){
t[++l]=j;
j=a[j];
}
for(int k=1;k<=l/2;k++){
ans[++f].x=t[k];ans[f].y=t[l+1-k];
swap(a[ans[f].x],a[ans[f].y]);
v[ans[f].x]=1;v[ans[f].y]=1;
}
}
}
out();//第一轮的操作输出
f=0;
pr();//将第一轮产生的小环归位
out();//将第二***作输出
return 0;
}
京公网安备 11010502036488号