在实际的评奖评优与考务系统中,如何按照多元维度公平、唯一地确定排名次序?洛谷 P1093 [NOIP2007 普及组]《奖学金》提供了一个标准的现实场景:根据三门课程总分、单科语文分以及学号先后来打破平局。这道题是信奥考查**结构体建模(struct)与多关键字自定义排序规则(严格弱序)**的标志性试题。对于 GESP 四级与 CSP-J 入门考生而言,掌握如何设计稳定的比较函数,将现实中的多级仲裁逻辑转化为优雅的计算机排序代码,是迈向复杂数据处理的必备基本功。
P1093 [NOIP2007 普及组] 奖学金
🔗 洛谷原题传送门:luogu-P1093 [NOIP2007 普及组] 奖学金
🔹 题目描述
某小学最近得到了一笔赞助,打算拿出其中一部分为学习成绩优秀的前 名学生发奖学金。期末,每个学生都有 门课的成绩:语文、数学、英语。先按总分从高到低排序,如果两个同学总分相同,再按语文成绩从高到低排序,如果两个同学总分和语文成绩都相同,那么规定学号小的同学排在前面,这样,每个学生的排序是唯一确定的。
任务:先根据输入的 门课的成绩计算总分,然后按上述规则排序,最后按排名顺序输出前五名学生的学号和总分。注意,在前 名同学中,每个人的奖学金都不相同,因此,你必须严格按上述规则排序。例如,在某个正确答案中,如果前两行的输出数据(每行输出两个数:学号、总分)是:
7 2795 279
这两行数据表示,学号为 的同学总分为 ,学号为 的同学总分为 。在不同的答卷中,双方总分都是 ,但学号为 的同学语文成绩高一些,所以排在前面。
🔹 输入格式
共 行。
第 行为一个正整数 (),表示该校参加评选的学生人数。
第 到 行,每行有 个空格隔开的数字,每个数字都在 到 之间,分别表示一个学生的语文、数学、英语成绩。学生学号按输入顺序编号为 (恰好是输入数据的行号减 )。
🔹 输出格式
共 行,每行是两个用空格隔开的正整数,依次表示前 名学生的学号和总分。
🔹 输入输出样例
输入 #1
690 67 8087 66 9178 89 9188 99 7767 89 6478 89 98
输出 #1
6 2654 2643 2582 2441 237
输入 #2
880 89 8988 98 7890 67 8087 66 9178 89 9188 99 7767 89 6478 89 98
输出 #2
8 2652 2646 2641 2585 258
🔹 说明/提示
数据规模与约定
对于 的数据,保证 。
🔹 题目深度剖析
1. 结构体与数据复合建模
每个学生实体包含多项相互绑定的属性:
- 学号(
id):在输入时按照顺序由 编号至 ,在排序后必须能准确定位原学生; - 三门单科成绩(
chinese,math,english):单科取值范围 ; - 总分(
total):三科成绩求和,最大为 。
若使用彼此分散的独立数组(如 int id[305], int chinese[305], int total[305]),在执行排序元素交换时极易出现多数组不同步的灾难性 Bug。在 C++ 中,定义 struct Student 结构体能将一个学生的所有字段打包为单个内存实体,无论是直接传参、赋值还是排序交换,都天然保持原子性与一致性。
2. 多关键字排序逻辑与优先级层次
题目给出了明确的三级决胜(Tie-breaking)条件,排序比较器函数 cmp(a, b) 必须按由主到次的规则依序判定:
- 第一主关键字:总分降序
若两个学生的 total不相等,总分更高者优先排前(即a.total > b.total); - 第二关键字:语文成绩降序
若总分相同( a.total == b.total),比较单科语文,语文分数高者优先排前(即a.chinese > b.chinese); - 第三关键字:学号升序
若总分与语文均相同,比较两人的初始学号,学号更小者优先排前(即 a.id < b.id)。
3. 严格弱序(Strict Weak Ordering)规范
C++ 标准库中的 std::sort 底层基于内省排序(Introsort),要求传入的二元比较谓词必须严格满足数学上的严格弱序:
- 非自反性
: cmp(x, x)必须恒为false; - 非对称性
:若 cmp(x, y)为true,则cmp(y, x)必为false; - 传递性
:若 cmp(x, y)为true且cmp(y, z)为true,则cmp(x, z)必为true。
高频避坑:绝不可写成 >= 或 <=!如果比较逻辑写成 a.total >= b.total,当两元素相等时 cmp(a, a) 将返回 true,破坏非自反性,导致 std::sort 迭代器越界发生段错误(Segmentation Fault)。必须使用严格的大于 > 或小于 <。
🔹 解题步骤与核心避坑指南
1. 学号的正确初始化与维护
学号并不是从键盘输入的属性,而是学生在输入序列中的行序():
2. 全局固定数组防越界,杜绝变长数组(VLA)
本题数据规模 :
根据 CCF GESP 与信息学奥赛规范,严禁在函数内使用局部动态变长数组 Student stu[n];;规范做法是定义常量 const int MAXN = 305;并在全局区声明静态数组Student stu[MAXN];。
3. 输出前 5 名边界保障
题目要求输出前 名学生的学号和总分。在标准测试中 ,为保证极端测试下的稳健性,可使用 min(n, 5) 作为输出循环上限:
🔹 完整参考代码 (C++)
🔹 复杂度深度分析
- 时间复杂度
: - 数据读入
:单层循环遍历 个学生,耗时 ; - 多关键字排序
:采用 std::sort对包含 个元素的结构体数组排序。单次比较耗时为 ,整体排序复杂度为 ; - 结果输出
:固定输出 次,耗时 ; - 综合时间复杂度
为 。在最坏规模 时, 次运算,耗时 ,瞬时完成。
- 数据读入
- 空间复杂度
: 全局静态数组大小为 ; 空间复杂度为 ,远低于题目的 限制。
🔹 总结与同类考点拓展
“奖学金”是多关键字排序的黄金模板题。解决此类问题的核心方法论可归纳为:
- 结构体一体化建模
:将需要协同排序的所有属性和原始身份标号(如学号、编号、时间戳)合并封装; - 比较器层级分支明确
:用阶梯式的 if (a.attr != b.attr) return a.attr > b.attr;依次收敛决策链; - 牢记非自反性原则
:使用严格的大于或小于运算符,确保排序稳定可靠。
经典同类推荐练习题:
luogu-P1051 [NOIP 2005 提高组] 谁拿了最多奖学金(结构体模拟与综合评分运算) luogu-P1068 [NOIP 2009 普及组] 分数线划定(多关键字排序与分数线下标截断) luogu-P1781 宇宙总统(结构体多关键字结合大整数位数与字典序比较) luogu-P1104 生日(年、月、日多层日期比较与逆序学号判定)
本站已收录超 900+ 篇计算机与算法专题。由于微信公众号不支持外部链接直接跳转,建议点击左下角「阅读原文」直达个人网站,即可使用全局检索(Ctrl+K)、在线复制代码与浏览完整知识库!
长按关注「OneCoder」公众号