当前位置: 首页 > news >正文

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

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

http://www.jsqmd.com/news/986330/

相关文章:

  • 非开挖内衬CIPP技术:2026商家推荐+用户案例教你选靠谱修复方案 - 品牌优选官
  • 河南铝单板厂家技术实力拆解:从产品到服务的硬核标准 - 奔跑123
  • 2026深圳黄金回收怎么选?五大正规门店,适配不同变现需求 - 奢侈品回收测评
  • 新手必看!2026年6月10日临沂黄金回收全攻略:大盘价911.71,金价大跌正是变现黄金的黄金时机! - 速递信息
  • QT5.14.2安装后第一件事:手把手教你配置项目目录与创建纯C控制台应用
  • 2026 东莞环保包装厂家实力排行榜 昆保达凭技术与产能稳居榜首 - 变量人生001
  • 告别跳转混乱!VSCode/Vim + Clangd 配置交叉编译头文件的保姆级避坑指南
  • RStudio里cat()和sink()用哪个?数据科学新手必看的文件输出避坑指南
  • 2026罗马尼亚各类签证代办深度解析:靠谱渠道选择与避坑指南 - 奔跑123
  • 告别Python依赖:将PP-HumanSeg轻量模型集成到你的C++桌面应用(附VS2019工程)
  • 信息学奥赛常见坑点复盘:以‘分数线划定’为例,聊聊多关键字排序的那些细节
  • 从菜鸟到高手:玩转Word/WPS表格与文本互转,这些隐藏技巧和常见坑你得知道
  • 2026年6月10北京黄金回收5家门店实测,金价大跌的同时您在卖黄金时选错靠谱商家,那就是亏上加亏了 - 速递信息
  • 2026年一体化泵闸厂家深度选型:如何为水利项目匹配最佳方案? - 热点速览
  • 保姆级教程:在蜂鸟E203上跑通riscv-tests(附VCS+Verdi波形调试技巧)
  • Peta vs 自研——为什么购买比构建更划算?
  • 北京军队文职培训机构多维横评:登科在线、红师教育、华图教育三家实力解析与选型参考 - 一知资讯
  • 2026年6月日照渔港美食店推荐指南:火爆美食,海鲜美食,平价美食公司优选! - 品牌鉴赏师
  • 管道光固化原位修复:2026选型攻略+商家推荐,避坑要点全掌握 - 品牌优选官
  • 2026年全球电子元器件展精选指南:德国慕尼黑/俄罗斯莫斯科/巴西/香港春季/印度/越南/韩国/摩洛哥/英国专业展推荐 - 品牌发掘
  • 2026常州奢侈品回收全品类攻略,天宁区靠谱门店优选添价收 - 薛定谔的梨花猫
  • Qt 5.12.6 在 Windows 10 上安装,为什么我建议你选 MinGW 而不是 MSVC?
  • 2026正规商标交易平台有哪些?备案、资质、服务查询指南 - 速递信息
  • 为什么越来越多招投标从业者选择谛听招标 - 谛听招标
  • 从安装到第一个C程序:用QT Creator 5.14.2快速搭建Windows C语言开发环境(附项目目录规划建议)
  • 闲置 LV 别乱卖!2026 广州包包回收避坑,高价正规门店出炉 - 薛定谔的梨花猫
  • 商用净水器租赁常见问题解答(2026最新专家版) - 热点速览
  • 广州律师事务所那么多,广东智谷律师事务所怎么样?选对律所看这几点 - 资讯焦点
  • 解决Windows动态库符号导出问题
  • 2026 乐平厨卫屋面地下室漏水瓷砖空鼓测评:吉修匠 99.8 分五星榜首 - 吉修匠