交换瓶子

来源: 第七届蓝桥杯省赛C++B组

算法标签 图论 环 置换群 贪心

题目描述

有 N 个瓶子,编号 1∼N,放在架子上。

比如有 5 个瓶子:

2 1 3 5 4
要求每次拿起 2 个瓶子,交换它们的位置。

经过若干次后,使得瓶子的序号为:

1 2 3 4 5
对于这么简单的情况,显然,至少需要交换 2 次就可以复位。

如果瓶子更多呢?你可以通过编程来解决。

输入格式

第一行包含一个整数 N,表示瓶子数量。

第二行包含 N 个整数,表示瓶子目前的排列状况。

输出格式

输出一个正整数,表示至少交换多少次,才能完成排序。

数据范围

1≤N≤10000,

输入样例1:

5
3 1 2 5 4

输出样例1:

3

输入样例2:

5
5 4 3 2 1

输出样例2:

2

思路

由样例1
​​​3 1 2 5 4​​​ 转为
​1 2 3 4 5​

对于瓶子顺序而言 则会有
3 对应正确顺序的3的位子 即当前的 2
2 对应正确顺序的2的位子 即当前的 1
1 对应正确顺序的2的位子 即当前的 3
这连接则形成一个闭环
总共有两个闭环

环内两个点的变动可以使得环分裂为2
环外的两个点的变动可以使得两个环合并为1

环最简的情况下是 自指,即存在n个环
这种情况同时也是 该题转换为正确顺序之后的情况
则可以遍历出当前所有环的数量 k
从k变更到n 需要n-k步骤
答案即为 n-k

在实际的操作过程当中,遍历环可以从1(i)出发每次指向​​i=a[i]​​即指向指定的目标

时间复杂度

实际从n^2优化到了n

C++ 代码

#include<iostream>
#include<cstdio>

using namespace std;

const int N=1e4+10;
bool st[N];
int a[N],n,k;

int main()
{
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];//瓶子初始序号

for(int i=1;i<=n;i++)//从每个正确序号出发
if(!st[i])//如果没有走过
{
k++;//环数量加一
for(int j=i;!st[j];j=a[j])//走完这个环 每次变更指向a[j] 即瓶子初始序号的第j个
st[j]=true;
}

cout<<n-k;//环最简情况为自指 则最多有n个环 当前有k个环 从K达到n则需要n-k次

return 0;
}