PAT 甲级 1067 Sort with Swap(0, i)

PAT 甲级 1067 Sort with Swap(0, i)

在这里插入图片描述

原题链接

大意是只能用交换0和其他数字的操作,对乱序的0~N-1进行排序,问最少需要多少次数。

这道本来是贪心算法的题,但是有趣就有趣在还可以用不相交集来理解,从而有了下面的写法

#include <bits/stdc++.h>
using namespace std;
int main()
{
#ifdef LOCAL
    freopen("input.txt", "r", stdin);
#endif
    int n;
    cin >> n;
    vector<int> num(n);
    for (int& i : num) cin >> i;
    // 由于元素是唯一的,没有重复,自然就形成了一些等价类。
    // 这些等价类表现为一个个环
    // 而且他们可以被分为两类:需要重排的等价类和不需要重排的等价类(只有一个元素的自环)
    // 用ctr_group表示需要重排的等价类个数
    // 用ctr_num表示需要排序的类中的元素个数之和
    // ctr_num==0的时候当然不需要交换,直接输出0即可
    // ctr_num不等于0的时候,需要看0是否指向自己(自成一个等价类)(因为0是用来进行操作的元素,所以比较特殊)
    // 0在需要交换的等价类里面的时候交换次数就是(ctr_num-1)+(ctr_gruop-1)
    // 0自己指向自己,并且ctr_num != 0也就是存在需要交换的等价类时,交换次数是(ctr_num-1)+(ctr_gruop-1)+2
    // 加的2分别是0交换出去和0交换回来两次
    vector<bool> has_checked(n, false);
    int ctr_group = 0;
    int ctr_num   = 0;
    for (int i = 0; i < n; ++i) {
        if (has_checked[num[i]] || num[i] == i)
            continue;
        else {
            int j = num[i];
            while (!has_checked[j]) {
                has_checked[j] = true;
                j              = num[j];
                ++ctr_num;
            }
            ++ctr_group;
        }
    }
    
    if (ctr_num == 0)
        cout << 0;
    else if (num[0] == 0)
        cout << ctr_num + ctr_group;
    else
        cout << ctr_num + ctr_group - 2;
}

附上贪心算法的解法和思路

一次交换最多能够使得除开0之外的一个元素归位,这个元素就是0所在的序号。比如用例中,0在7号位置,那么第一次交换肯定能把7复位。贪心的思路就是每次都尽量让一个元素归位。什么时候是例外呢?就是0在0号位置但还没有排序完成的情况。这个时候需要让0和没有排序完成的数字进行一次交换,从而可以继续上面的过程,直到排序完成。于是就有了下面贪心(和模拟)的写法。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100010;
int pos[maxn];
int main()
{
#ifdef LOCAL
    freopen("input.txt", "r", stdin);
#endif
    int n, ans = 0;
    cin >> n;
    int left = n - 1, num;  // left存放除了0之外不在本位上的数的个数
    for (int i = 0; i < n; ++i) {
        cin >> num;
        pos[num] = i;                // num所处的位置为i
        if (num == i && num != 0) {  // 如果除0之外有在本位上的数字
            --left;
        }
    }
    int k = 1;          // k存在除0以外不在本位上的最小的数字
    while (left > 0) {  // 只要有数字不在本位上
        // 如果0在本位上,寻找一个当前不在本位上的数与0交换
        if (pos[0] == 0) {
            while (k < n) {
                if (pos[k] != k) {
                    std::swap(pos[0], pos[k]);
                    ++ans;
                    break;
                }
                ++k;  // 判断k是否在本位
            }
        }
        // 只要0不在本位,就把0所在位置的数的当前位置与0的位置交换
        while (pos[0] != 0) {
            std::swap(pos[0], pos[pos[0]]);
            ++ans;
            --left;
        }
    }
    cout << ans << endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值