题解:洛谷 P1459 三值的排序
【题目来源】
洛谷:P1459 [USACO2.1] 三值的排序 Sorting a Three-Valued Sequence - 洛谷
【题目描述】
排序是一种很频繁的计算任务。现在考虑最多只有三值的排序问题。一个实际的例子是,当我们给某项竞赛的优胜者按金银铜牌排序的时候。在这个任务中可能的值只有三种 1,2,3。我们用交换的方法把他排成升序的。
写一个程序计算出,给定的一个 1,2,3 组成的数字序列,排成升序所需的最少交换次数
【输入】
第一行一个正整数 n,表示奖牌个数。接下来 n 行,每行一个 [1,3] 内的整数,表示奖牌。
【输出】
输出一行一个整数,表示排成升序所需的最少交换次数。
【输入样例】
9
2
2
1
3
3
3
2
3
1
【输出样例】
4
【核心思想】
-
问题分析:给定一个由 \(1, 2, 3\) 组成的序列,要求通过交换操作将其排成升序(所有 \(1\) 在前,\(2\) 在中,\(3\) 在后),求最少交换次数。排序后 \(1\) 的区间为 \([1, num[1]]\),\(2\) 的区间为 \([num[1]+1, num[1]+num[2]]\),\(3\) 的区间为 \([num[1]+num[2]+1, n]\)。
-
算法选择:
- 贪心交换策略:优先处理"直接互换"(一次交换解决两个错位),再处理"三角互换"(两次交换解决三个错位)
- 错位分类:将错位的元素按所在区间和目标区间分类,统计六种错位类型
-
关键步骤:
- 统计数量:遍历序列,统计 \(num[1], num[2], num[3]\)(各数字出现次数),确定排序后三个区间的边界
- 第一轮:直接互换(一次交换修复两个位置):
- \([1,2]\) 互换:在 \(1\) 的区间内找 \(2\),在 \(2\) 的区间内找 \(1\),配对交换
- \([1,3]\) 互换:在 \(1\) 的区间内找 \(3\),在 \(3\) 的区间内找 \(1\),配对交换
- \([2,3]\) 互换:在 \(2\) 的区间内找 \(3\),在 \(3\) 的区间内找 \(2\),配对交换
- 第二轮:三角互换(一次交换修复一个位置,剩余错位形成三角循环):
- 在 \(1\) 的区间内找非 \(1\) 的元素,与后面区间中的 \(1\) 交换
- 在 \(2\) 的区间内找非 \(2\) 的元素,与后面区间中的 \(2\) 交换
- 输出结果:总交换次数 \(ans\)
-
时间/空间复杂度:
- 时间复杂度:\(O(n^2)\),最坏情况下需要双重循环查找配对元素
- 空间复杂度:\(O(n)\),存储序列数组
-
贪心交换的核心思想:
- 直接互换优先:\(a[i]=2, a[j]=1\) 且 \(i\) 在 \(1\) 区间、\(j\) 在 \(2\) 区间时,一次交换同时修复两个位置,成本最低
- 三角循环处理:直接互换后,剩余的错位元素形成三角循环(如 \(1\) 区间有 \(2\),\(2\) 区间有 \(3\),\(3\) 区间有 \(1\)),需要两次交换修复三个位置
- 区间边界固定:排序后各数字的位置区间由数量唯一确定,无需实际排序即可知道每个元素的目标位置
- 贪心最优性:优先执行直接互换不会破坏后续更优解,且每次直接互换减少两个错位,是局部最优选择
- 适用于有限取值排序、最小交换次数、错位修复类问题
【解题思路】

【算法标签】
普及- #贪心
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int n, num[5], a[1005], ans=0;
int main()
{
cin >> n; // 输入n
for (int i=1; i<=n; i++) { // 依次输入n个值
cin >> a[i];
num[a[i]]++; // 同时记录每个数的数量
}
for (int i=1; i<=num[1]; i++) { // 在排序后1的范围和2的范围内
for (int j=num[1]+1; j<=num[1]+num[2]; j++) {
if (a[i]==2 && a[j]==1) { // 查找[2,1]和[1,2]的情况
swap(a[i], a[j]); // 进行对调
ans++;
break;
}
}
}
for (int i=1; i<=num[1]; i++) { // 在排序后1的范围和3的范围内
for (int j=num[1]+num[2]+1; j<=n; j++) {
if (a[i]==3 && a[j]==1) { // 查找[3,1]和[1,3]的情况
swap(a[i], a[j]); // 进行对调
ans++;
break;
}
}
}
for (int i=num[1]; i<=num[1]+num[2]; i++) { // 在排序后2的范围和3的范围内
for (int j=num[1]+num[2]+1; j<=n; j++) {
if (a[i]==3 && a[j]==2) { // 查找[3,2]和[2,3]的情况
swap(a[i], a[j]); // 进行对调
ans++;
break;
}
}
}
for (int i=1; i<=num[1]; i++) { // 在排序后1的范围和2、3范围内
for (int j=num[1]+1; j<=n; j++) {
if (a[i]!=1 && a[j]==1) { // 查找不等于1和等于1的
swap(a[i], a[j]); // 进行对调
ans++;
break;
}
}
}
for (int i=num[1]+1; i<=num[1]+num[2]; i++) { // 在排序后2的范围和3的范围内
for (int j=num[1]+num[2]+1; j<=n; j++) {
if (a[i]!=2 && a[j]==2) { // 查找不等于2和等于2的
swap(a[i], a[j]); // 进行对调
ans++;
break;
}
}
}
cout << ans << endl; // 输出调换次数
return 0;
}
【运行结果】
9
2
2
1
3
3
3
2
3
1
4
浙公网安备 33010602011771号