题单 · 习题解析

洛谷 P1093 奖学金

总分 → 语文 → 学号,输出前五名,三关键字排序的模板

原题:洛谷 P1093出自 第 10 章 排序:冒泡 → 归并 → 快排 的题单题面本地存档:2026-10-18
⚠ 先自己写一遍,再往下看

这一页不是标准答案,是一条阶梯:把大多数人真实会写出的第一版摆出来, 看它错在哪、慢在哪,再一步一步改。所以它对「已经自己动手撞过一次墙」的人最有用。
还没写过的话,先去写 —— 写出来的哪怕是错的,也比直接看这一页收获大。

0题目原文(本地存了一份)

为什么要在这儿抄一份题面

原题在洛谷上(页头有链接)。别人的网站不归我们管,打不开、改版、题号调整都可能发生。 所以每个解析页都把题面转录一份存在本地,跟着仓库一起进版本库。

转录自洛谷 P1093,日期见页头。两边不一致时信原站。

题目描述

某小学最近得到了一笔赞助,打算拿出其中一部分为学习成绩优秀的前 $5$ 名学生发奖学金。期末,每个学生都有 $3$ 门课的成绩:语文、数学、英语。先按总分从高到低排序,如果两个同学总分相同,再按语文成绩从高到低排序,如果两个同学总分和语文成绩都相同,那么规定学号小的同学排在前面。任务:先根据输入的 $3$ 门课的成绩计算总分,然后按上述规则排序,最后按排名顺序输出前五名学生的学号和总分。

输入格式

共 $n+1$ 行。第 $1$ 行为一个正整数 $n$($5 \le n \le 300$)。第 $2$ 到 $n+1$ 行,每行有 $3$ 个用空格隔开的数字,分别表示学号为 $j-1$ 的学生的语文、数学、英语成绩(每个 $0 \sim 100$)。

输出格式

共 $5$ 行,每行两个整数,表示前 $5$ 名学生的学号和总分。

输入输出样例

输入

5
100 90 80
90 90 90
80 80 80
70 70 70
60 60 60

输出

1 270
2 270
3 240
4 210
5 180
学生版:一页看懂这道题

这道题考什么:三关键字排序。总分 → 语文 → 学号,每层都是「分得出胜负就 return,分不出就往下一层」。

最容易错的两处

  1. 总分相同忘了比语文,或者把语文的升降序写反。
  2. 忘了学号兜底,导致同分同语文时顺序不确定。

能 AC 的:p1093。三层 cmp:总分降序 → 语文降序 → 学号升序,最后只输出前 5。

下面每一步里灰色的「深入」块是折叠的:那是这些结论怎么实测出来的,第一遍可以跳过。

1三层 cmp

p1093.cpp★ 这一版就能 AC
#include <bits/stdc++.h>
using namespace std;
struct Student {
int id;
int cn, math, en;
int total() const { return cn + math + en; }
};
int main() {
int n;
if (!(cin >> n)) return 0;
vector<Student> a(n);
for (int i = 0; i < n; i++) {
a[i].id = i + 1;
cin >> a[i].cn >> a[i].math >> a[i].en;
}
sort(a.begin(), a.end(), [](const Student &x, const Student &y) {
if (x.total() != y.total()) return x.total() > y.total();
if (x.cn != y.cn) return x.cn > y.cn;
return x.id < y.id;
});
for (int i = 0; i < 5 && i < (int)a.size(); i++) {
cout << a[i].id << " " << a[i].total() << "\n";
}
return 0;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果
★ 三关键字 = 三行 if

if (x.total() != y.total()) return x.total() > y.total();

if (x.cn != y.cn) return x.cn > y.cn;

return x.id < y.id;

⇒ 第 10 章那句「相等就往下一层」,这里走了三层。