Problem Description
Given any permutation of the numbers {0, 1, 2,..., N−1}, it is easy to sort them in increasing order. But what if Swap(0, *) is the ONLY operation that is allowed to use? For example, to sort {4, 0, 2, 1, 3} we may apply the swap operations in the following way:
Swap(0, 1) => {4, 1, 2, 0, 3}
Swap(0, 3) => {4, 1, 2, 3, 0}
Swap(0, 4) => {0, 1, 2, 3, 4}
Now you are asked to find the minimum number of swaps need to sort the given permutation of the first N nonnegative integers.
Input Specification
Each input file contains one test case, which gives a positive N (≤105) followed by a permutation sequence of {0, 1, ..., N−1}. All the numbers in a line are separated by a space.
Output Specification
For each case, simply print in a line the minimum number of swaps need to sort the given permutation.
Sample Input
10
3 5 7 2 6 4 9 0 8 1
Sample Output
9
Solution
题意理解:给定 0 到 N - 1 的一个排序,要求仅使用 0 和另一个数交换的方式来达到非递减排序,求最少要交换的次数。
很容易想到一个模拟的方法根据题意写出排序算法并计算交换次数即可,但非常显然地超时。
![[../../assets/Pasted image 20220826175546.png]]
第一次尝试(TLE):
#include <stdio.h>
#include <stdlib.h>
void Swap(int *a, int *b)
{
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
}
int IsOrdered(int A[], int N)
{
int i, flag = 1;
for (i = 0; i < N && flag; i++) {
if (A[i] != i) flag = 0;
}
return flag;
}
int Find(int A[], int N, int num)
{
int i;
for (i = 0; i < N; i++)
if (A[i] == num)
return i;
}
int FindNew(int A[], int N)
{
int i;
for (i = 0; i < N; i++)
if (A[i] != i) return i;
}
int SwapSort(int A[], int N)
{
int i, position0, times = 0;
while (!IsOrdered(A, N)) {
position0 = Find(A, N, 0);
if (A[0] != 0) {
Swap(&A[position0], &A[Find(A, N, position0)]);
} else {
Swap(&A[position0], &A[FindNew(A, N)]);
}
times++;
}
return times;
}
int main()
{
int i, N, A[100000];
scanf("%d", &N);
for (i = 0; i < N; i++) scanf("%d", &A[i]);
printf("%d", SwapSort(A, N));
return 0;
}
第二次尝试(AC):
由表排序的思想可知,每次 Swap( 0 , * ) 最后都会成一个环。
- 当环内只有一个元素时,已经落在正确位置上,不交换
- 当环内含有 0 时,0 要和环内剩下的其他元素交换,交换次数就等于环内元素减一
- 当环内不含有 0 时,先将 0 交换进入环内,再与每个环内元素交换一次,交换次数等于环内元素加一
不需要真的去 Swap( 0 , * ) ,只需要用表排序将每个环内交换的次数累加起来即为答案。
程序框架:
- 读入
A[i] - 构造
T[i]:存放的是i在A中的下标 - 计算结果
- 输出结果
表排序详解:
- 外层循环遍历数组下标,进入循环后,如果
A[i] != i就进入环内排序- 如果该环内没有 0 ,初始化
cnt为 1 - 如果环内有 0 ,初始化
cnt为 1 j = i进入内循环- 循环结束条件是
T[j] != j,最后把刚进入环内的那个元素放在当前j的上一个位置p处 - 进入循环,
A[j] = A[T[j]];,把正确的元素落上位置 - 记下当前所作操作的下标为
p - 更新
j为T[j] T[p] = p;- 交换一次计数器加一
- 循环结束条件是
- 最后退出循环落好位置后加上一次环的交换次数
- 如果该环内没有 0 ,初始化
- 全部遍历完毕后输出总交换次数
#include <stdio.h>
#include <stdlib.h>
int SortWithSwap(int A[], int N, int T[])
{
int i, j, p, tmp, sum, cnt;
sum = 0;
for (i = 0; i < N; i++) {
if (A[i] != i) {
if (A[T[i]] == 0) cnt = -1;
else cnt = 1;
j = i;
tmp = A[j];
while (T[j] != j) {
A[j] = A[T[j]];
p = j;
j = T[j];
T[p] = p;
cnt++;
}
A[p] = tmp;
sum += cnt;
}
}
return sum;
}
int main()
{
int i, N, A[100000], T[100000];
scanf("%d", &N);
for (i = 0; i < N; i++) scanf("%d", &A[i]);
for (i = 0; i < N; i++) T[A[i]] = i;
printf("%d", SortWithSwap(A, N, T));
return 0;
}