UVa 299 Train Swapping
题目分析
本题描述了一个经典的列车车厢重排问题。在一个老式火车站中,有一种特殊的工作称为 “列车交换员” (Train Swapper\texttt{Train Swapper}Train Swapper),其工作是通过交换相邻车厢的位置,将列车车厢按照编号 111 到 LLL 的顺序排列。题目要求计算将给定序列通过相邻交换排序所需的最少交换次数。
实际上,这个问题等价于 计算一个排列的逆序对数。因为每次交换相邻两个元素可以消除恰好一个逆序,而最少交换次数就等于原始排列中的逆序总数。
逆序的定义:在一个序列中,如果存在一对下标 i<ji < ji<j 满足 a[i]>a[j]a[i] > a[j]a[i]>a[j],则称 (i,j)(i, j)(i,j) 为一个逆序对。
解题思路
题目中给出的两个代码实现了两种不同的方法来计算最少交换次数。
方法一:冒泡排序统计交换次数
第一个代码直接模拟了冒泡排序的过程,并在每次交换相邻元素时累加计数器。由于冒泡排序每次比较并交换相邻逆序对,最终的交换次数就等于逆序对数。
算法步骤:
- 从最后一个位置开始,逐轮将当前未排序部分的最大元素 “冒泡” 到末尾。
- 在每一轮中,从左到右比较相邻元素,如果前一个大于后一个,则交换并增加计数。
- 最终计数即为最少交换次数。
这种方法的时间复杂度为 O(L2)O(L^2)O(L2),由于 L≤50L \leq 50L≤50,完全可行。
方法二:依次寻找最小元素并移动到前面
第二个代码采用了另一种思路:按照编号 1,2,…,L1, 2, \dots, L1,2,…,L 的顺序,依次将当前需要的车厢 “移动” 到序列的前面。每次找到目标车厢的当前位置,将其移动到前面所需交换的次数等于它当前的位置索引(假设从 000 开始计数),然后将该车厢从序列中删除。
算法步骤:
- 对于 iii 从 111 到 LLL:
- 在当前序列中查找数值为 iii 的元素,得到其下标 jjj(从 000 开始)。
- 将 jjj 累加到答案中(因为需要将 iii 经过 jjj 次相邻交换移动到最前面)。
- 从序列中删除该元素。
- 输出累加的总和。
这个方法本质上也是在计算逆序数,因为将元素 iii 移动到前面需要经过它前面的所有元素,而这些元素都与 iii 构成了逆序对。
两种方法的比较
| 方法 | 核心思想 | 时间复杂度 | 代码简洁度 |
|---|---|---|---|
| 冒泡排序 | 模拟冒泡,统计交换次数 | O(L2)O(L^2)O(L2) | 较简单 |
| 删除法 | 依次查找最小元素并删除 | O(L2)O(L^2)O(L2) | 稍复杂 |
两种方法都能正确求解,且对于 L≤50L \leq 50L≤50 的数据范围都足够高效。
代码实现
// Train Swapping
// UVa ID: 299
// Verdict: Accepted
// Submission Date: 2016-05-09
// UVa Run Time: 0.000s
//
// 版权所有(C)2016,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
vector<int> trains; // 存储当前车厢序列
// 使用冒泡排序统计最少相邻交换次数(即逆序对数)
int sortAndCount()
{
int swaps = 0; // 交换次数计数器
// 外层循环:一共需要进行 L-1 轮冒泡
for (int i = trains.size() - 1; i > 0; i--)
// 内层循环:将当前轮次中最大的元素向右移动
for (int j = 0; j < i; j++)
// 如果相邻两个元素逆序,则交换并计数
if (trains[j] > trains[j + 1])
{
swaps++;
swap(trains[j], trains[j + 1]);
}
return swaps; // 返回总交换次数
}
int main(int argc, char *argv[])
{
// 加速输入输出
cin.tie(0);
cin.sync_with_stdio(false);
int cases; // 测试用例个数
cin >> cases;
while (cases--)
{
int number; // 当前测试用例的车厢数量
cin >> number;
trains.clear(); // 清空向量
// 读取车厢序列
int index;
for (int i = 1; i <= number; i++)
{
cin >> index;
trains.push_back(index);
}
// 输出结果
cout << "Optimal train swapping takes " << sortAndCount() << " swaps.\n";
}
return 0;
}
// Train Swapping
// UVa ID: 299
// Verdict: Accepted
// Submission Date: 2016-05-09
// UVa Run Time: 0.000s
//
// 版权所有(C)2016,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
vector < int > trains;
int cases, number;
int sortAndCount()
{
int swaps = 0;
for (int i = 1; i <= number; i++)
for (int j = 0; j < trains.size(); j++)
if (trains[j] == i)
{
swaps += j;
trains.erase(trains.begin() + j);
break;
}
return swaps;
}
int main(int argc, char *argv[])
{
cin.tie(0);
cin.sync_with_stdio(false);
cin >> cases;
while (cases--)
{
cin >> number;
trains.clear();
int index;
for (int i = 1; i <= number; i++)
{
cin >> index;
trains.push_back(index);
}
cout << "Optimal train swapping takes " << sortAndCount() << " swaps.\n";
}
return 0;
}
总结
本题的核心是识别出问题本质为 求逆序对数,因为相邻交换排序的最小次数等于逆序总
数。理解了这一点后,可以用多种方法求解。题目给出的 L≤50L \leq 50L≤50 的限制使得 O(L2)O(L^2)O(L2) 的算法完全足够,无需使用更高效的归并排序或树状数组等 O(LlogL)O(L \log L)O(LlogL) 的方法。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)