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;
}