图论(环)-蓝桥杯省赛C++B组-交换瓶子

图论(环)-蓝桥杯省赛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

题解:

对 数 组 a , 用 数 组 下 标 i 对 应 a [ i ] , 表 示 应 当 把 a [ i ] 与 交 换 成 i 。 对数组a,用数组下标i对应a[i],表示应当把a[i]与交换成i。 aia[i]a[i]i

从 图 论 角 度 考 虑 , 下 标 i 为 起 点 , 点 a [ i ] 为 终 点 , 构 成 一 条 从 i 到 a [ i ] 的 边 。 样 例 一 图 如 下 : 从图论角度考虑,下标i为起点,点a[i]为终点,构成一条从i到a[i]的边。样例一图如下: ia[i]ia[i]
在这里插入图片描述在这里插入图片描述
形 成 两 个 环 。 形成两个环。

若 把 1 和 3 交 换 , 也 就 是 a [ 1 ] 和 a [ 2 ] 交 换 , 序 列 变 成 1   3   2   5   4 , 左 边 环 变 成 : 若把1和3交换,也就是a[1]和a[2]交换,序列变成1\ 3\ 2\ 5\ 4,左边环变成: 13a[1]a[2]1 3 2 5 4,
在这里插入图片描述
这 个 过 程 就 是 交 换 了 下 标 为 1 和 2 的 出 边 所 指 向 的 点 , 1 的 出 边 指 向 了 2 原 本 指 向 的 1 , 也 就 是 1 已 经 正 确 归 位 , 2 的 出 边 指 向 了 1 原 本 指 向 的 3 , 环 一 分 为 二 。 这个过程就是交换了下标为1和2的出边所指向的点,1的出边指向了2原本指向的1,也就是1已经正确归位,\\2的出边指向了1原本指向的3,环一分为二。 121211213

对 任 意 点 的 数 量 的 这 样 的 环 , 交 换 任 意 两 点 的 出 边 所 指 向 的 点 均 会 将 该 环 一 分 为 二 。 如 下 图 : 对任意点的数量的这样的环,交换任意两点的出边所指向的点均会将该环一分为二。如下图:
在这里插入图片描述

反 之 , 两 个 环 可 以 合 并 成 一 个 环 。 而 我 们 目 标 是 将 所 有 环 分 裂 成 本 身 , 就 是 要 a [ i ] = i , 就 必 须 不 断 分 裂 。 反之,两个环可以合并成一个环。而我们目标是将所有环分裂成本身,就是要a[i]=i,就必须不断分裂。 a[i]=i

因 此 , 通 过 这 样 的 方 式 交 换 , 可 以 最 快 将 序 列 交 换 成 有 序 序 列 。 因此,通过这样的方式交换,可以最快将序列交换成有序序列。

要 求 交 换 的 次 数 , 假 设 我 们 有 c n t 个 环 , 就 是 要 求 使 c n t 个 环 变 成 n 个 环 的 最 小 步 数 。 每 次 分 裂 增 加 一 个 环 , 最 终 的 步 数 就 是 n − c n t 。 因 此 , 统 计 环 的 数 量 即 可 。 要求交换的次数,假设我们有cnt个环,就是要求使cnt个环变成n个环的最小步数。\\每次分裂增加一个环,最终的步数就是n-cnt。因此,统计环的数量即可。 cnt使cntnncnt

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define P pair<int,int>
#define x first
#define y second
#define ll long long
#define inf 0x7fffffff
using namespace std;
const int N=1e4+10;
int n,a[N];
bool vis[N];

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);

    int cnt=0;///统计环的个数
    for(int i=1;i<=n;i++)
        if(!vis[i])
        {
            int j=i;
            while(!vis[j])
            {
                vis[j]=true;
                j=a[j];
            }
            cnt++;
        }

    cout<<n-cnt<<endl;
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值