【2025年莆田市第五期C++专项第三轮选拔真题(初中组)】魔法石
题目背景
在遥远的“数域王国”,有一个特殊的试炼场——交换广场。这里有一个传奇的试炼,称为“数列排序挑战”。试炼的主角是一名年轻的勇士,名叫阿尔法。阿尔法得到了一个任务:他需要将一组神秘的数字石从小到大排序。每个数字石上刻着不同的数字,且这些数字之间没有任何重复。
每当阿尔法站在广场的中央时,他会发现这些数字石被随机放置在广场的不同位置,而阿尔法手中的魔法杖赋予了他一种特殊能力——交换魔法。这个魔法允许他每次交换任意两颗数字石的位置,但每次交换后,他只能得到一个非常小的提示:“当前石头序列离完全排序还有几步之遥。”
阿尔法决定尽可能高效地完成这个任务,希望尽量少地使用交换魔法,以节省时间和体力。那么问题来了:阿尔法最少需要多少次交换,才能让这些数字石按从小到大的顺序排列呢?
简单来说定一个数列 a a a,这个数列满足 a i ≠ a j a_i \not =a_j ai=aj( i ≠ j i\not=j i=j),现在要求你把这个数列从小到大排序,每次允许你交换其中任意一对数,请问最少需要几次交换?
输入格式
第一行是一个整数,代表数字个数 n n n。
第二行有 n n n 个整数用空格分隔开,表示数列 a a a。
输出格式
只有一行,包含一个数,表示最少的交换次数。
输入输出样例 #1
输入 #1
8
8 23 4 16 77 1 53 100
输出 #1
5
输入 #2
5
1 4 3 2 5
输出 #3
1
说明/提示
数据规模与约定
对于20%的数据, 1 ≤ n ≤ 2 1 \le n \le 2 1≤n≤2 , 1 < a i < 2 1\lt a_i\lt2 1<ai<2;
对于另 40 % 40\% 40% 的数据,保证 1 ≤ n ≤ 1000 1\le n\le1000 1≤n≤1000, 1 < a i < 1000 1\lt a_i\lt1000 1<ai<1000, a i ≤ a i − 1 a_i \le a_{i-1} ai≤ai−1;
对于 100 % 100\% 100% 的数据,保证 1 ≤ n ≤ 10 5 1\le n\le10^5 1≤n≤105, 1 < a i < 10 5 1\lt a_i\lt10^{5} 1<ai<105。
问题重述
给定一个不包含重复元素的数列,每次可以交换任意两个元素的位置,问最少需要多少次交换才能将数列排序为升序。
解题思路
这个问题可以转化为计算数列中的环的数量。每个环至少需要交换(环的大小-1)次才能归位。因此,最少交换次数等于所有环的大小减一的总和。
具体步骤:
- 建立映射关系:首先对原数组进行排序,然后记录每个元素在排序后数组中的位置。
- 寻找环:遍历原始数组,通过映射关系找到每个元素应该在的位置,从而发现环。
- 计算交换次数:对于每个大小为k的环,需要k-1次交换来归位所有元素。
代码实现
#include <bits/stdc++.h>
using namespace std;
struct Element {
int value; // 元素的值
int index; // 元素在原数组中的位置
};
int main() {
ios::sync_with_stdio(false); // 加速输入输出
cin.tie(nullptr); // 解除cin与cout的绑定
int n;
cin >> n;
vector<Element> elements(n + 1); // 1-based索引
vector<int> sortedPosition(n + 1); // 记录排序后每个元素的位置
// 读取输入并记录原始位置
for (int i = 1; i <= n; ++i) {
cin >> elements[i].value;
elements[i].index = i;
}
// 按值排序
sort(elements.begin() + 1, elements.end(),
[](const Element& a, const Element& b) {
return a.value < b.value;
});
// 建立排序后位置到原始位置的映射
for (int i = 1; i <= n; ++i) {
sortedPosition[elements[i].index] = i;
}
int swapCount = 0;
vector<bool> visited(n + 1, false); // 标记是否访问过
// 寻找环并计算交换次数
for (int i = 1; i <= n; ++i) {
if (!visited[i]) {
int j = i;
int cycleSize = 0;
while (!visited[j]) {
visited[j] = true;
j = sortedPosition[j];
cycleSize++;
}
if (cycleSize > 1) {
swapCount += (cycleSize - 1);
}
}
}
cout << swapCount << endl;
return 0;
}
代码解释
- 输入处理:使用快速输入方法读取数据,并记录每个元素的原始位置。
- 排序:对元素按值进行排序,得到排序后的位置映射。
- 环检测:通过遍历原始位置数组,检测环的存在。对于每个未访问的元素,追踪其应该在的位置,直到回到起点,形成一个环。
- 计算交换次数:每个环需要(环大小-1)次交换来归位所有元素。
复杂度分析
时间复杂度:O(n log n)(排序的时间复杂度)
空间复杂度:O(n)(存储元素和位置映射)