蓝桥杯-交换瓶子

交换瓶子 (第七届蓝桥杯省赛JAVA A组)

有 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: 4 3 1 5 2

目标序列:1 2 3 4 5

1. 贪心+模拟:

我们任何时候的交换都只能交换有意义的数字,否则肯定不是最优的
比如序列A中交换1 和 5,交换后1 和 5两个数字都没有到本该属于它们的位置上,这就属于无效交换

所以我们只需要遍历序列A,将本该属于这个位置上的数字换过来,交换次数加1,这样遍历到序列A末尾一定就会变成 1 2 3 4 5这个目的状态
在这个过程中我们需要维护一个哈希表来记录每个数字当前所在的位置

代码:

#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i = a;i < b;i++)
#define per(i,a,b) for(int i = b - 1;i >= a;i--)
#define all(x) (x).begin(),(x).end()
#define fi first
#define se second
typedef long long ll;
typedef pair<int,int> PII;
typedef vector<int> VI;

const int N = 10010;
int a[N], hs[N], n;
int main() {
    scanf("%d", &n);
    rep(i,1,n+1) {
        scanf("%d", &a[i]);
        hs[a[i]] = i;
    }
    int res = 0;
    rep(i,1,n+1) {
        if(a[i] != i) {
            int t = a[i];
            swap(a[i],a[hs[i]]);
            hs[t] = hs[i];
            res++;
        }
    } 
    printf("%d\n", res);
    return 0;
}

注意: 在交换的过程中,哈希表hs[]也需要改变

2. 贪心+图论+并查集

序列A: 4 3 1 5 2
目的序列: 1 2 3 4 5

序列A

可知4在1的位置,3在2的位置,1在3的位置,5在4的位置,2在5的位置
表示:
4 -> 1, 3 -> 2, 1 -> 3, 5 -> 4, 2 -> 5

整理一下会发现构成一个环:

那么为什么会构成环呢?

不难发现序列A中的每个数值都对应一个目的序列中的一个位置,根据我们的定义也就是有一条出边,而序列A中的每个数值也是目的序列中的数值,只不过位置不同,那么这个数值也一定会被其他数所指向,也就是会有一条入边

目的序列:

1 -> 1, 2 -> 2, 3 -> 3, 4 -> 4, 5 -> 5

我们的目的就是从序列A的图表示到目的序列的图表示,也就是每个数构成一个自环

根据我们之前提到的贪心策略:每次交换一定要至少将一个数值归到正确位置,也就是每次交换我们都要分出来一个自环,剩下的数依然会构成环

比如交换序列A中的1 和 3,

此时一个数3归到正确的位置

序列A: 4 1 3 5 2
目的序列: 1 2 3 4 5

4 -> 1, 1 -> 2, 3 -> 3, 5 -> 4, 2 -> 5

由此我们可以发现,每次根据贪心策略进行交换瓶子都会分离出一个自环,知道分出 n 个自环为止

综上所述,我们需要从原序列A中的若干个环(不一定是一个,可以自己模拟一下题目样例)变成 n 个环, 交换的次数就是 (n - 原序列中的环的个数), 即为答案

本题中如果想要求原序列A中的环的个数可以使用并查集,因为只要两个数相连,之后这两个数与其他数必定会构成环, 所以仿照并查集求连通块的数量的写法即可

代码:

#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i = a;i < b;i++)
#define per(i,a,b) for(int i = b - 1;i >= a;i--)
#define all(x) (x).begin(),(x).end()
#define fi first
#define se second
typedef long long ll;
typedef pair<int,int> PII;
typedef vector<int> VI;

const int N = 10010;
int fa[N], n;

//并查集模板
int find(int x) {
    return x == fa[x] ? fa[x] : fa[x] = find(fa[x]);
}

int main() {
    scanf("%d", &n);
    //初始化并查集
    rep(i,1,n+1) fa[i] = i;

    //处理输入并进行集合合并
    rep(i,1,n+1) {
        int x;
        scanf("%d", &x);    
        int fx = find(x), fy = find(i);
        if(fx != fy) fa[x] = fy;
    }
    //查找连通块的数量
    int cnt = 0;
    rep(i,1,n+1) if(fa[i] == i) cnt++;

    //n减去环的数量就是答案(本题中环的数量与连通块的数量相同)
    printf("%d\n", n-cnt);
    return 0;
}
posted @ 2023-01-09 23:19  junlin623  阅读(166)  评论(0编辑  收藏  举报