
CP Weekly Challenge
CCC 2016 J5/S2 Tandem Bicycle Solution: Sort First, Then Pairing Has Only One Answer
Sort both teams, pair in the same order for the minimum or opposite order for the maximum, and prove why with an exchange argument.
竞赛编程每周挑战
CCC 2016 J5/S2 Tandem Bicycle 题解:排序之后,配对就只剩一种答案
先排序:同序配对得到最小总速度,逆序配对得到最大总速度,再用交换论证证明正确性。
Two countries each send N cyclists to ride tandem bicycles, and every bike's speed is decided by "the faster of its two riders". The question: pair them so the total speed of all N bikes is as small as possible — or as large as possible. CCC 2016 J5/S2 Tandem Bicycle looks like it requires comparing all N! pairings. What it really tests is one greedy line: sort first, then pair. Once sorted, "same-order pairing minimizes, opposite-order pairing maximizes" can be proved by a single exchange argument — one of the most common greedy shapes in contests.
Where This Fits in the CCC DSA Map
- Level: Junior J5 ≡ Senior S2 (the same problem serves both levels)
- Pattern: Sorting + Greedy Pairing
- Prerequisite: sorting lists, loops,
max(), Big-O basics (Python basics: input, operators, and conditions, Python Lists: append() vs +) - What this teaches: prove a greedy rule with an "one exchange tells the direction" argument, reducing N! pairings to one scan after sorting
- Where it appears again: the greedy topic (planned), the same-week DSA-025 Time on Task greedy proof (python-notes), CPW-026 Time on Task (planned)
What to Study Next
- Related CP Weekly problems: CCC 2013 J4 Time on Task (coming soon — greedy counting under a budget), and "Why Does Greedy Sorting Always Work? The Exchange Argument with Time on Task" (Python Notes, same week)
- Related Python / C++ foundations: Python Basics: Input, Operators, and Conditions, Python Lists: append() vs +
- Related DSA roadmap article: Greedy and Binary Search Patterns (planned)
What the Problem Is Asking
The first line gives q: q=1 asks for the minimum total speed, q=2 for the maximum. The second line gives N (1 ≤ N ≤ 100), the third the N speeds of Dmojistan, the fourth the N speeds of Pegland (each between 1 and 10⁶). Each tandem bike pairs one rider from each country, and its speed is the larger of the two riders' speeds. Output one integer: the requested total speed.
Official sample 1 (q=1): Dmoj [5 1 4], Peg [6 2 4] → pair 5-6, 1-2, 4-4, total 6+2+4 = 12.
Official sample 2 (q=2): same data, maximize → pair 5-2, 1-6, 4-4, total 5+6+4 = 15.
Official sample 3 (q=2, N=5): Dmoj [202 177 189 589 102], Peg [17 78 1 496 540] → 2016.
Where Beginners Often Get Stuck
- Enumerating pairings for real: N! pairings sounds hopeless. After sorting, the answer is uniquely determined — there is nothing to "choose".
- Adding both riders' speeds: the bike speed is decided by the faster rider — it is
max(a, b); computing a+b (which gives 22 on the sample) solves the wrong objective. - Forgetting to reverse for the maximum: q=2 needs one list ascending and the other descending; skipping the reverse still computes the minimum's shape.
- Reversing both lists: reversing both preserves the original pairs and does nothing for the maximum — if you want speed, reverse exactly one.
- Worrying about the proof: "same-order minimizes" feels like a guess. The exchange argument below shows it holds for all data, not by luck.
Core Algorithm Idea
Do the arithmetic of two bikes first. Take any two Dmoj riders a ≤ b and any two Peg riders c ≤ d. Only two pairings exist:
same-order: max(a, c) + max(b, d)
crossed: max(a, d) + max(b, c)
As shown under "Why the Algorithm Is Correct", for every a ≤ b and c ≤ d, same-order ≤ crossed. So every time a "crossed" pair is swapped back into same order, the total does not increase — pairing both ascending by index minimizes the total. The maximizer is the mirror image: one list ascending, the other descending.
This also explains why summing first kills the greedy: if the bike speed were a+b, every pairing would give the same total, and greedy reasoning would have nothing to hold onto. Reading the objective — max or sum — is step one of this problem family.
State / Data Structure Design
q: objective type, 1 = minimum total, 2 = maximum total.n: riders per country, i.e., the number of tandem bikes.dmoj,peg: the two speed lists, sorted (and possibly reversed), then paired by index.total: the sum ofmax(dmoj[i], peg[i])over all pairs.
No need to record "who paired with whom" — the pairing is uniquely decided by the sorted order.
Algorithm Steps
- Read q, n, and the two speed lists of length n each.
- Sort both lists in ascending order.
- If q=2, reverse
peg(exactly one list). - Walk the indices, adding
max(dmoj[i], peg[i])intototal. - Print
total.
Sample Check with the Official Data
Walk both objectives on the sample data (sort [5 1 4] into [1 4 5], [6 2 4] into [2 4 6]):
| Index | dmoj | peg (same order) | Bike | peg (reversed) | Bike |
|---|---|---|---|---|---|
| 0 | 1 | 2 | 2 | 6 | 6 |
| 1 | 4 | 4 | 4 | 4 | 4 |
| 2 | 5 | 6 | 6 | 2 | 5 |
| Total | 12 (min) | 15 (max) |
Same-order gives 12, matching sample 1; reversed gives 15, matching sample 2. Sample 3's 2016 comes from the same "ascending + descending" pairing. This table is the whole core of greedy pairing: sort once, scan once per objective.
Python Solution
q = int(input())
n = int(input())
dmoj = list(map(int, input().split()))
peg = list(map(int, input().split()))
dmoj.sort()
peg.sort()
if q == 2:
peg.reverse()
total = 0
for i in range(n):
total += max(dmoj[i], peg[i])
print(total)
This matches the curriculum notebook (p2006, sorting and greedy reasoning) line for line — sort, reverse when asked, accumulate. total ≤ 100 × 10⁶ = 10⁸, which Python integers handle easily; a 32-bit signed int also just barely fits, though building the long long habit is safer.
C++ Solution
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
int main() {
int q;
cin >> q;
int n;
cin >> n;
vector<int> dmoj(n);
for (int i = 0; i < n; i++) {
cin >> dmoj[i];
}
vector<int> peg(n);
for (int i = 0; i < n; i++) {
cin >> peg[i];
}
sort(dmoj.begin(), dmoj.end());
sort(peg.begin(), peg.end());
if (q == 2) {
reverse(peg.begin(), peg.end());
}
long long total = 0;
for (int i = 0; i < n; i++) {
total += max(dmoj[i], peg[i]);
}
cout << total << '\n';
return 0;
}
Mirrors the curriculum pseudocode step by step: sort is O(N log N), the accumulation O(N), and the official limits finish in well under a millisecond. Sorting aux space: Python's sort uses O(N) worst case, C++ introsort O(log N) stack.
Why the Algorithm Is Correct
- Each bike's speed is indeed a max: the statement defines the bike speed as the larger of the two riders' speeds, matching
max(dmoj[i], peg[i]). - Same-order pairing minimizes the total: take any two "crossed" pairs (a,d) and (b,c) with a ≤ b and d ≥ c (as they appear in the sorted lists). Swap them back to (a,c), (b,d). For every a ≤ b and c ≤ d, max(a,c) + max(b,d) ≤ max(a,d) + max(b,c): check the four size cases one by one (for example, when a ≤ c ≤ b ≤ d, the left side is c + d and the right side is d + b, and c ≤ b makes the left ≤ right). Any pairing can be sorted into same order by finitely many such "descent swaps", each never increasing the total, so the same-order pairing is minimal.
- Reversed pairing maximizes the total: swapping the two sides of the same inequality gives max(a,d) + max(b,c) ≥ max(a,c) + max(b,d) — the reversed pairing is the mirror image of the same argument.
- No better pairing is missed: sorting fixes the relative order of both sequences; every index-based pairing corresponds to exactly one "same-order" or "opposite-order" shape, so nothing better is left out.
Therefore the printed total is exactly the requested minimum or maximum total speed.
Complexity Analysis
- Time O(N log N): the two sorts dominate; the pairing pass is O(N). At N=100 this runs in well under a millisecond.
- Space O(N): the two speed lists (see above for sort aux space).
- If N grew to 10⁵, this would still be the standard solution — the proof and the sorting both scale unchanged.
Common Mistakes
- Summing both riders' speeds — misreading the objective as a+b (the sample would give 22).
- Forgetting the reverse for q=2 — or reversing both lists (which reverses nothing).
- Sorting only one list — the unsorted side makes the pairing depend on input order.
- Memorizing "same-order minimizes" without the proof — under a different objective (like a sum) the greedy dies instantly; be ready to retell the exchange argument.
- Misreading the input — q, n, then two lines of speeds; line up with the samples row by row.
Review Questions
- Complete the exchange argument for all four relative orders of a ≤ b and c ≤ d, and explain why same-order pairing is never worse than crossed pairing.
- If the bike speed were changed to the sum of both riders' speeds, would sort-and-pair still work? Why or why not?
- Why does reversing both lists fail to maximize the total? Explain with the pairs at indices 0 and N−1.
- This problem shares one greedy habit with Time on Task (finish as many tasks as possible within a budget). What is that sentence? (Hint: see DSA-025.)
- With N ≤ 10⁵ and speeds ≤ 10⁹, what are the time and space complexities? Is there a better asymptotic order?
两个国家各派出 N 名骑手骑双人自行车,每辆车的速度由"较快的那个人"决定。问:怎么配对,才能让 N 辆车的总速度最小?或者最大?CCC 2016 的 J5/S2 Tandem Bicycle 看起来有 N! 种配对方案要比较,真正考的却是一句贪心口诀:先排序,再配对。排序之后,"同序配对得最小、逆序配对得最大"可以被一条交换论证严格证明——这是竞赛里最常见的贪心形态之一。
本题在 CCC DSA 地图里的位置
- Level: Junior J5 ≡ Senior S2(同一道题,两个级别共用)
- Pattern: Sorting + Greedy Pairing(排序 + 贪心配对)
- Prerequisite: 列表排序、循环、
max()、Big-O 基础(Python 基础:输入、运算符与条件、Python 列表 append() 和 + 的区别) - What this teaches: 用"交换一对就知方向"的论证证明贪心正确,把 N! 种配对化简为排序后的一次扫描
- Where it appears again: 贪心专题 (planned)、同周发布的 DSA-025 Time on Task 贪心证明(python-notes)、CPW-026 Time on Task(planned)
下一步推荐
如果你已经理解这道题,可以继续看:
- 相关 CP Weekly 真题:CCC 2013 J4 Time on Task(coming soon,预算内最多完成几个任务的贪心计数)、《贪心排序为什么一定对?从 Time on Task 讲清交换论证》(Python Notes,同周发布)
- 相关 Python / C++ 基础:Python 基础:输入、运算符与条件、Python 列表 append() 和 + 的区别
- 相关 DSA 专题:Greedy and Binary Search Patterns(planned)
题目要我们做什么
第一行一个整数 q:q=1 求总速度最小值,q=2 求最大值。第二行 N(1 ≤ N ≤ 100),第三行 Dmojistan 的 N 个速度,第四行 Pegland 的 N 个速度(每人速度 1 到 10⁶)。每辆双人自行车由两国各一人组成,车速 = 两人速度的较大值。输出一个整数:所求的总速度。
官方样例 1(q=1):Dmoj [5 1 4],Peg [6 2 4] → 最优配对 5-6、1-2、4-4,总速度 6+2+4 = 12。
官方样例 2(q=2):同样数据求最大 → 配对 5-2、1-6、4-4,总速度 5+6+4 = 15。
官方样例 3(q=2,N=5):Dmoj [202 177 189 589 102],Peg [17 78 1 496 540] → 2016。
初学者容易卡在哪里
- 真的去枚举配对:N! 种配对一想就放弃。其实排序之后答案唯一确定,根本不用"选"配对。
- 把两人速度相加:题目说车速由较快者决定,是
max(a, b);如果按 a+b 计算(样例会得 22),就解错了目标。 - 最大值时忘记反转:q=2 要"一个升序、一个降序";忘记反转、两个都升序,算出来的还是最小值的思路。
- 两个列表都反转:反转两个列表等于保持原来的配对,对最大值毫无作用——想提速的话只能反转一个。
- 担心证明:觉得"同序最小"是猜的。下面的交换论证会说明它对所有数据成立,不是碰运气。
核心算法概念
先把两辆车的账算清楚。取 Dmoj 中任意两人 a ≤ b,Peg 中任意两人 c ≤ d,只有两种配法:
同序:max(a, c) + max(b, d)
交叉:max(a, d) + max(b, c)
可以证明(见"正确性说明"):对任意 a ≤ b、c ≤ d,同序 ≤ 交叉。也就是说,只要把"谁该和谁配"的次序理顺,每交换回一对,总速度就不会变大——于是"两个都升序、按下标配对"让总速度最小;而总速度最大的配法恰好是它的镜像:一个升序、另一个降序。
这也解释了为什么不能先求和再排序:如果车速是 a+b,无论怎么配对总和都一样,贪心立刻失效——看清目标是 max 还是 sum,是这类题的第一步。
状态定义
q:目标类型,1 = 最小总速度,2 = 最大总速度。n:每国的骑手数,即双人自行车数。dmoj、peg:两国速度列表,排序(必要时反转)后按下标一一配对。total:所有配对max(dmoj[i], peg[i])的和。
不需要记录"谁配了谁"——配对由排序结果唯一决定。
算法步骤
- 读入 q、n 和两个长度各为 n 的速度列表。
- 两个列表各自升序排序。
- 若 q=2,把
peg反转(只反转一个)。 - 遍历下标 i,累加
max(dmoj[i], peg[i])到total。 - 输出
total。
官方样例核对
用样例数据把两种目标各走一遍(先把 [5 1 4] 排成 [1 4 5],[6 2 4] 排成 [2 4 6]):
| 下标 | dmoj | peg(同序) | 车速 | peg(逆序) | 车速 |
|---|---|---|---|---|---|
| 0 | 1 | 2 | 2 | 6 | 6 |
| 1 | 4 | 4 | 4 | 4 | 4 |
| 2 | 5 | 6 | 6 | 2 | 5 |
| 合计 | 12(最小) | 15(最大) |
同序合计 12 与样例 1 一致;逆序合计 15 与样例 2 一致。样例 3 的 2016 同样由"升序 + 降序"配对得到。这张表就是贪心 pairing 的全部核心:排序一次,两种答案各扫一遍。
Python 解法
q = int(input())
n = int(input())
dmoj = list(map(int, input().split()))
peg = list(map(int, input().split()))
dmoj.sort()
peg.sort()
if q == 2:
peg.reverse()
total = 0
for i in range(n):
total += max(dmoj[i], peg[i])
print(total)
要点:代码与课程教案(p2006 排序与贪心推理)的参考实现一致——排序、按需反转、一次累加。total ≤ 100 × 10⁶ = 10⁸,Python 整数无压力;如果移植到其他语言注意用 32 位有符号整数也刚好够用,但养成 long long 的习惯更稳妥。
C++ 解法
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
int main() {
int q;
cin >> q;
int n;
cin >> n;
vector<int> dmoj(n);
for (int i = 0; i < n; i++) {
cin >> dmoj[i];
}
vector<int> peg(n);
for (int i = 0; i < n; i++) {
cin >> peg[i];
}
sort(dmoj.begin(), dmoj.end());
sort(peg.begin(), peg.end());
if (q == 2) {
reverse(peg.begin(), peg.end());
}
long long total = 0;
for (int i = 0; i < n; i++) {
total += max(dmoj[i], peg[i]);
}
cout << total << '\n';
return 0;
}
要点:与教案伪代码逐步对应;sort 为 O(N log N)、累加为 O(N),官方数据规模下毫秒级完成。排序的辅助空间:Python sort 最坏 O(N),C++ introsort 栈空间 O(log N)。
正确性说明
- 每辆车确实由 max 决定:题面规定车速为两骑手速度的较大值,算法中的
max(dmoj[i], peg[i])与题面一致。 - 同序配对使总速度最小:取任意两对"交叉"的配对 (a,d) 与 (b,c),其中 a ≤ b、d ≥ c(都在各自升序列表中)。把它们换回同序 (a,c)、(b,d)。对任意 a ≤ b、c ≤ d 有 max(a,c) + max(b,d) ≤ max(a,d) + max(b,c):分四种大小情形逐一展开即可验证(例如 a ≤ c ≤ b ≤ d 时,左边 = c + d、右边 = d + b,由 c ≤ b 得左边 ≤ 右边)。任意配对都可通过有限次这样的"逆序交换"排成同序,每次交换总速度不增,故同序配对最小。
- 逆序配对使总速度最大:把上式左右对调即得 max(a,d) + max(b,c) ≥ max(a,c) + max(b,d),逆序配对是同一论证的镜像。
- 覆盖全部方案:排序固定了两个序列的相对次序,任一下标配对方案都对应一种"排序后整体同序/逆序"的形态,故没有遗漏更优解。
因此输出的 total 就是题目所求的最小或最大总速度。
复杂度分析
- 时间 O(N log N):瓶颈是两个排序;配对累加 O(N)。N=100 时不足毫秒。
- 空间 O(N):两个速度列表(排序辅助空间见上)。
- 若把 N 放大到 10⁵,本算法依然是标准解法——排序的稳定性与证明都与 N 无关。
常见错误
- 按两人速度求和:目标函数看错,把
max写成a+b,样例会算出 22。 - q=2 时忘记反转,或两个列表都反转(等于没反转)。
- 只对一国排序:另一国保持输入序,配对随输入变化而错。
- 把"同序最小"当经验背诵:换一道目标函数不同的题(如求和)贪心就失效;要会复述交换论证。
- 读入错位:q、n、两行速度共四行,格式与样例逐行对应。
复习问题
- 补全交换论证的四种大小情形(a ≤ b、c ≤ d 的全部相对顺序),说明为什么同序配对总不劣于交叉配对。
- 如果把题目改成"车速 = 两人速度之和",排序配对还适用吗?为什么?
- 为什么"两个列表都反转"对求最大值无效?用下标 0 和 N−1 的配对说明。
- 本题与 Time on Task(预算内尽可能多完成任务)共享同一个贪心习惯,那句话是什么?(提示:见 DSA-025)
- 若 N ≤ 10⁵、速度 ≤ 10⁹,本算法的时间和空间复杂度各是多少?有没有更优的渐进阶?
Questions or feedback?
Have a question about this article or want to suggest an improvement? Send us a private message.
有问题或建议?
如果你对本文有疑问,或希望提出改进建议,请给我们发送私密留言。