news 2026/6/10 14:54:53

NOIP2009普及组真题解析:用C++搞定分数线划定,从冒泡到STL sort的四种解法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
NOIP2009普及组真题解析:用C++搞定分数线划定,从冒泡到STL sort的四种解法

NOIP2009普及组真题解析:用C++搞定分数线划定,从冒泡到STL sort的四种解法

在信息学奥赛的备赛过程中,排序算法是每个选手必须掌握的核心技能。2009年NOIP普及组的"分数线划定"题目,恰好为我们提供了一个绝佳的练习平台,让我们能够从多个角度理解排序算法的实际应用。这道题目看似简单,却蕴含着丰富的算法思想和工程实践价值。

对于初学者而言,这道题的价值不仅在于完成题目要求,更在于通过不同解法的对比,深入理解算法效率、代码可读性和工程实践之间的平衡。我们将从最基础的冒泡排序开始,逐步过渡到更高级的STL算法,完整呈现一个竞赛选手的思维进化过程。

1. 问题分析与基础解法

1.1 题目要求解析

题目要求我们根据考生的报名号和成绩,确定面试的分数线。具体规则是:计划录取人数的150%(向下取整)名考生的分数即为分数线,所有不低于该分数线的考生都将进入面试。当有同分考生时,按报名号升序排列。

关键数据范围:

  • 考生人数n:1 ≤ n ≤ 5000
  • 计划录取人数m:1 ≤ m ≤ n
  • 报名号k:1000 ≤ k ≤ 9999
  • 成绩s:1 ≤ s ≤ 100

1.2 冒泡排序实现

作为最直观的排序方法,冒泡排序虽然效率不高(O(n²)),但对于n≤5000的数据规模完全足够。我们先来看基础实现:

#include <iostream> using namespace std; int main() { int k[5005], s[5005], n, m; cin >> n >> m; for(int i = 1; i <= n; ++i) cin >> k[i] >> s[i]; // 冒泡排序 for(int i = 1; i <= n - 1; ++i) for(int j = 1; j <= n - i; ++j) { if(s[j] < s[j+1] || (s[j] == s[j+1] && k[j] > k[j+1])) { swap(s[j], s[j+1]); swap(k[j], k[j+1]); } } int line = s[int(m*1.5)]; int ct = 0; for(int i = 1; i <= n; ++i) if(s[i] >= line) ct++; cout << line << ' ' << ct << endl; for(int i = 1; i <= ct; ++i) cout << k[i] << ' ' << s[i] << endl; return 0; }

这种解法的优势在于:

  • 代码逻辑直观,易于理解
  • 不依赖任何高级语言特性
  • 适合算法初学者理解排序过程

但缺点也很明显:

  • 排序效率较低
  • 代码可读性和维护性较差
  • 数据耦合度高(报名号和成绩分开存储)

2. 结构体与自定义排序

2.1 使用结构体组织数据

为了提高代码的可读性和可维护性,我们引入结构体来封装考生信息:

struct Student { int id; // 报名号 int score; // 成绩 };

这种封装方式使得相关数据被组织在一起,大大提高了代码的清晰度。同时,我们可以利用C++的sort函数进行高效排序。

2.2 自定义比较函数

STL的sort函数允许我们通过自定义比较函数来实现复杂的排序规则:

bool compare(const Student &a, const Student &b) { if(a.score != b.score) return a.score > b.score; // 成绩降序 return a.id < b.id; // 报名号升序 }

完整实现代码如下:

#include <iostream> #include <algorithm> using namespace std; struct Student { int id, score; }; bool compare(const Student &a, const Student &b) { if(a.score != b.score) return a.score > b.score; return a.id < b.id; } int main() { Student stu[5005]; int n, m; cin >> n >> m; for(int i = 0; i < n; ++i) cin >> stu[i].id >> stu[i].score; sort(stu, stu + n, compare); int line = stu[int(m*1.5) - 1].score; int count = 0; for(int i = 0; i < n; ++i) if(stu[i].score >= line) count++; cout << line << ' ' << count << endl; for(int i = 0; i < count; ++i) cout << stu[i].id << ' ' << stu[i].score << endl; return 0; }

这种解法的优势:

  • 代码结构清晰,可读性强
  • 排序效率高(O(nlogn))
  • 数据组织更合理

3. 进阶:STL算法与Lambda表达式

3.1 使用Lambda表达式

C++11引入的Lambda表达式可以让我们的代码更加简洁:

sort(stu, stu + n, [](const Student &a, const Student &b) { return a.score > b.score || (a.score == b.score && a.id < b.id); });

3.2 稳定排序的应用

在某些特殊情况下,我们可能需要保持相等元素的原始顺序。这时可以使用stable_sort:

// 先按报名号排序(稳定) stable_sort(stu, stu + n, [](const Student &a, const Student &b) { return a.id < b.id; }); // 再按成绩排序(稳定) stable_sort(stu, stu + n, [](const Student &a, const Student &b) { return a.score > b.score; });

虽然对于本题来说,普通的sort已经足够,但了解stable_sort的特性对于解决更复杂的问题很有帮助。

4. 性能分析与优化

4.1 不同解法的性能对比

解法类型时间复杂度空间复杂度代码复杂度适用场景
冒泡排序O(n²)O(n)教学、小规模数据
STL sortO(nlogn)O(n)通用场景
稳定排序O(nlogn)O(n)需要保持原始顺序
计数排序O(n+k)O(k)数据范围小的情况

4.2 计数排序的特殊解法

当成绩范围有限(如本题1-100分)时,可以使用计数排序实现O(n)时间复杂度的解法:

#include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> scores[101]; // 每个分数对应一个报名号列表 int n, m; cin >> n >> m; for(int i = 0; i < n; ++i) { int id, score; cin >> id >> score; scores[score].push_back(id); } // 对每个分数的报名号列表排序 for(int i = 1; i <= 100; ++i) sort(scores[i].begin(), scores[i].end()); int total = 0, line = 0; for(int i = 100; i >= 1; --i) { total += scores[i].size(); if(total >= int(m * 1.5)) { line = i; break; } } // 重新计算实际人数(可能有更多人同分) total = 0; for(int i = 100; i >= line; --i) total += scores[i].size(); cout << line << ' ' << total << endl; for(int i = 100; i >= line; --i) for(int id : scores[i]) cout << id << ' ' << i << endl; return 0; }

这种解法在特定条件下(成绩范围小)性能最优,但代码复杂度较高,通用性不如基于比较的排序。

5. 工程实践建议

5.1 代码风格与可读性

在竞赛编程中,良好的代码风格同样重要:

  • 使用有意义的变量名(如studentCount而非n)
  • 适当添加注释说明关键步骤
  • 保持一致的缩进和代码布局

5.2 调试技巧

排序类题目常见的调试问题:

  • 边界条件处理(如第m*1.5名正好有多个同分考生)
  • 比较函数的正确性(确保满足严格弱序)
  • 数组下标越界(特别是从0开始还是从1开始)

5.3 测试用例设计

针对本题,应该准备以下类型的测试用例:

  1. 常规情况(有明确分数线)
  2. 同分考生较多的情况
  3. 边界情况(n和m的最小/最大值)
  4. 所有考生同分的情况

例如:

// 测试用例1:常规情况 5 3 1001 90 1002 85 1003 95 1004 90 1005 80 // 测试用例2:多人同分 6 4 2001 80 2002 80 2003 85 2004 75 2005 85 2006 90

在实际比赛中,建议先手动计算预期结果,再与程序输出对比。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 14:24:52

身边事伍福家园获点赞

伍福家园获得点赞&#xff0c;与其践行的伍福理念、提供的特色产品以及开展的相关活动都紧密相关。伍福理念传递正能量伍福家园计划以伍福人生为目标&#xff0c;从自我心服开始&#xff0c;从家庭做起&#xff0c;带动社区&#xff0c;融聚社会更多有识之士&#xff0c;传递伍…

作者头像 李华
网站建设 2026/6/10 14:21:55

莱香肩颈功夫:内外同调的肩颈养护逻辑

在当代&#xff0c;长期久坐、低头看电子设备已成为常态&#xff0c;肩颈僵硬、酸痛、头晕等亚健康问题愈发普遍。莱香肩颈功夫作为一款聚焦肩颈康养的项目&#xff0c;核心逻辑是中医经络理论 植萃发酵技术 内外双调养护&#xff0c;并非简单的肌肉放松按摩&#xff0c;下面…

作者头像 李华
网站建设 2026/6/10 14:19:08

客户沟通会议记录转写实战评测:7款录音提取文字做工作总结横评

做客户沟通和客户分析的朋友都知道&#xff0c;每天最头疼的不是拜访客户&#xff0c;而是拜访完之后的整理工作。一场1小时的客户访谈&#xff0c;录音文件往往要花2-3小时才能听完、记完、整理成结构化报告。遇到多方言客户、技术术语、会议中多人同时发言的情况&#xff0c;…

作者头像 李华
网站建设 2026/6/10 14:17:14

2026 年,海南财税公司代办十佳企业,客户口碑与续费率排名

在2026年&#xff0c;海南的财税服务圈里&#xff0c;客户口碑和续约情况成了判断一家公司靠不靠谱的两个硬指标。过去几年&#xff0c;不少海南财税公司靠着办事快、懂政策&#xff0c;把客户一点点留住。评价一好&#xff0c;老客户也愿意继续合作&#xff0c;这种反馈其实很…

作者头像 李华
网站建设 2026/6/10 14:13:51

python3.5-数据存储与运算-输入与输出

输入input语句&#xff08;函数&#xff09;的功能就是获取键盘输入的数据&#xff0c;据图用法为&#xff1a;s input(提示信息)print语句&#xff08;函数&#xff09;的功能就是将数据输出到控制台&#xff0c;据图语法为&#xff1a;print(数据...)注意&#xff1a;无论键…

作者头像 李华
网站建设 2026/6/10 14:13:31

探索团队协作模式下的skills技能群,实现组织效能提升

技能名称触发词上游输入核心输出product-req-writer写需求、PRD、业务需求、需求编制用户原始需求7个子需求文档 1个索引文件requirement-review评审需求、需求评审、检查PRD、PRD评审product-req-writer 输出的 PRD 文档评审报告&#xff08;S1/S2/S3问题清单&#xff09; 可…

作者头像 李华