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

用C++解NOIP真题:P1068分数线划定,从冒泡到STL sort的四种解法对比

用C++解NOIP真题:P1068分数线划定,从冒泡到STL sort的四种解法对比

在信息学奥赛(NOIP/CSP)的备战过程中,排序算法是每位选手必须掌握的核心技能。2009年NOIP普及组的《分数线划定》一题,看似简单却暗藏玄机——它要求选手在处理多重排序规则时,能够灵活选择最优解法。这道题的数据规模虽然不大(最多5000条记录),但恰恰为我们提供了绝佳的教学案例:从最基础的冒泡排序开始,逐步升级到STL的高阶用法,甚至探索混合算法的可能性。

对于初学者而言,这道题的价值不仅在于"AC"(通过测试),更在于理解不同解法的适用场景与性能差异。本文将带您深入剖析四种典型解法:传统冒泡排序、结构体结合STL sort、两趟稳定排序(stable_sort)以及计数排序与插入排序的混合应用。每种方法都有其独特的实现逻辑和优化思路,我们将从代码复杂度、执行效率、内存占用等多个维度进行对比,帮助您在竞赛中做出更明智的选择。

1. 基础解法:冒泡排序的双重条件实现

让我们从最朴素的冒泡排序开始。虽然在实际竞赛中很少会真正使用这种O(n²)的算法,但理解它的实现原理对夯实基础至关重要。在分数线划定问题中,我们需要先按成绩降序排列,成绩相同时再按报名号升序排列——这种双重条件的排序规则,正是考察的重点。

#include <bits/stdc++.h> using namespace std; #define N 5005 int main() { int k[N], s[N], n, m, line, ct = 0; 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]); } } line = s[int(m*1.5)]; 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; }

关键点解析:

  • 双重条件判断:s[j] < s[j+1] || (s[j] == s[j+1] && k[j] > k[j+1])这一行代码完美体现了题目要求的排序规则
  • 交换逻辑:当条件满足时,需要同时交换成绩和报名号两个数组的值
  • 时间复杂度:最坏情况下需要进行约5000×5000=25,000,000次比较和交换

提示:虽然冒泡排序在本题数据规模下仍能通过,但在实际竞赛中应尽量避免使用,除非题目明确限制算法选择。

2. 结构体与STL sort的优雅解法

当数据需要关联多个属性时,结构体是C++中最自然的表达方式。结合STL的sort算法,我们可以写出更简洁、更易维护的代码。这种解法的核心在于自定义比较函数,它决定了排序的规则。

#include <bits/stdc++.h> using namespace std; #define N 5005 struct Stu { int k, s; // k:报名号 s:成绩 }; bool cmp(Stu &a, Stu &b) { if(a.s == b.s) // 分数相同时 return a.k < b.k; // 报名号小的在前 else // 分数不同时 return a.s > b.s; // 成绩高的在前 } int main() { Stu stu[N]; int n, m, line, ct = 0; cin >> n >> m; for(int i = 1; i <= n; ++i) cin >> stu[i].k >> stu[i].s; sort(stu+1, stu+1+n, cmp); // 使用自定义比较函数排序 line = stu[int(m*1.5)].s; for(int i = 1; i <= n; ++i) if(stu[i].s >= line) ct++; cout << line << ' ' << ct << endl; for(int i = 1; i <= ct; ++i) cout << stu[i].k << ' ' << stu[i].s << endl; return 0; }

性能对比:

指标冒泡排序STL sort
时间复杂度O(n²)O(nlogn)
代码复杂度中等简单
内存占用较低中等
可维护性较差优秀
适用数据规模<1万无限制

这种解法的优势不仅在于性能提升,更在于代码的可读性和扩展性。如果需要添加新的排序条件,只需修改cmp函数即可,主逻辑完全不受影响。

3. 稳定排序的应用:两趟排序策略

在某些特殊场景下,我们需要保持相同关键字的原始顺序,这时stable_sort就派上用场了。虽然本题并不严格要求稳定性,但了解这种解法有助于拓宽思路。

#include <bits/stdc++.h> using namespace std; #define N 5005 struct Stu { int k, s; }; bool cmp_k(const Stu &a, const Stu &b) { return a.k < b.k; // 报名号升序 } bool cmp_s(const Stu &a, const Stu &b) { return a.s > b.s; // 成绩降序 } int main() { Stu stu[N]; int n, m, line, ct = 0; cin >> n >> m; for(int i = 1; i <= n; ++i) cin >> stu[i].k >> stu[i].s; // 先按次要条件排序,再按主要条件稳定排序 stable_sort(stu+1, stu+1+n, cmp_k); stable_sort(stu+1, stu+1+n, cmp_s); line = stu[int(m*1.5)].s; for(int i = 1; i <= n; ++i) if(stu[i].s >= line) ct++; cout << line << ' ' << ct << endl; for(int i = 1; i <= ct; ++i) cout << stu[i].k << ' ' << stu[i].s << endl; return 0; }

稳定排序的特点:

  • 保持相等元素的相对顺序
  • 时间复杂度同样是O(nlogn),但常数因子通常比sort大
  • 适用于需要多次排序且要保持前次排序结果的场景
  • 在本题中,先按报名号排序,再按成绩稳定排序,等价于单次多条件排序

注意:虽然两趟排序能得到正确结果,但在竞赛中应优先考虑单次多条件排序,除非题目明确要求保持稳定性。

4. 混合算法:计数排序+插入排序的巧妙组合

当数据范围有限时(如本题成绩不超过100分),计数排序可以发挥O(n)的时间复杂度优势。结合插入排序处理同分学生的报名号排序,这种混合算法在特定条件下性能最优。

#include <bits/stdc++.h> using namespace std; int score[105][5005] = {}; // score[i][0]存储长度 int main() { int k, s, n, m, line, ct = 0; cin >> n >> m; // 计数排序+插入排序 for(int i = 1; i <= n; ++i) { cin >> k >> s; // 将k插入score[s]数组,并保持升序 int& len = score[s][0]; score[s][++len] = k; for(int j = len; j > 1; --j) { if(score[s][j] < score[s][j-1]) swap(score[s][j], score[s][j-1]); else break; } } // 确定分数线 int lnum = int(m*1.5); for(int i = 100; i >= 0; --i) { ct += score[i][0]; if(ct >= lnum) { line = i; break; } } cout << line << ' ' << ct << endl; // 输出结果 for(int i = 100; i >= line; --i) for(int j = 1; j <= score[i][0]; ++j) cout << score[i][j] << ' ' << i << endl; return 0; }

算法分析:

  • 计数排序部分:O(n)时间复杂度处理成绩分布
  • 插入排序部分:最坏情况下O(k²),k为同分人数,但本题中k≤5000
  • 空间复杂度:O(max_score×n),当成绩范围很大时不适用
  • 优势:当成绩范围小而n很大时,效率远超常规排序算法

适用场景判断:

情况描述推荐算法
成绩范围小(如0-100),n很大计数+插入排序
成绩范围大,n中等(≤10⁵)STL sort单次排序
需要保持相同成绩的原始顺序stable_sort两趟
算法限制(如禁用STL)冒泡或其他基础排序

5. 竞赛中的实战选择策略

在真实的NOIP/CSP竞赛环境中,选择哪种解法不仅取决于算法效率,还需要考虑以下因素:

代码实现速度:

  • 结构体+sort写法通常最快实现
  • 冒泡排序虽然效率低,但在时间紧迫时可能更快写出
  • 混合算法实现复杂,容易出错,适合有充分时间检查时使用

调试难度:

  1. 结构体解法:变量组织清晰,调试最容易
  2. 两趟排序:需要注意排序顺序是否正确
  3. 混合算法:多维数组操作容易出界,需要仔细检查
  4. 冒泡排序:虽然简单,但边界条件容易出错

鲁棒性考量:

  • STL sort经过充分优化,几乎不会出现性能问题
  • 自己实现的排序需要注意特殊测试用例:
    • 所有成绩相同
    • 所有报名号相同
    • 极端数据(如n=1或n=5000)
// 竞赛推荐写法示例 #include <bits/stdc++.h> using namespace std; struct Candidate { int id, score; bool operator<(const Candidate& rhs) const { return score != rhs.score ? score > rhs.score : id < rhs.id; } }; int main() { int n, m; cin >> n >> m; vector<Candidate> candidates(n); for(auto& c : candidates) cin >> c.id >> c.score; sort(candidates.begin(), candidates.end()); int cutoff = candidates[(int)(m*1.5)-1].score; int count = count_if(candidates.begin(), candidates.end(), [cutoff](const Candidate& c) { return c.score >= cutoff; }); cout << cutoff << ' ' << count << '\n'; for(int i = 0; i < count; ++i) cout << candidates[i].id << ' ' << candidates[i].score << '\n'; }

优化技巧:

  • 使用vector替代原生数组更安全
  • 重载operator<比单独写cmp函数更简洁
  • 使用count_if算法统计人数更符合现代C++风格
  • 统一使用标准输入输出流(关闭同步后可提速)

在实际比赛中,我通常会准备一个排序算法的模板代码,遇到需要排序的题目时,只需根据具体需求调整比较逻辑即可。这种标准化的工作流程可以大幅减少实现时间,把更多精力留给真正的算法难题。

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

相关文章:

  • 告别数据不平衡:用CTGAN的‘条件生成器’为你的表格数据生成高质量合成样本
  • 基于 Windows + Ubuntu 练习 MuJoCo 模拟
  • 保姆级教程:用安信可ESP32S3开发板,把闲置USB摄像头变成无线监控(支持手机浏览器查看)
  • 明明插了麦克风却没声音?这些坑你踩了几个?
  • Stable Baselines3:5分钟掌握PyTorch强化学习框架
  • 告别配置混乱!用Apollo Profiles统一管理Spring Boot多环境配置(附Idea/Eclipse实战)
  • 基础采集设备
  • 2026年即墨区马桶疏通客服电话及服务指南 - 品牌排行榜
  • 2021年量产的时间窗口:曲速科技在推理赛道形成先发积累
  • Vim党福音:用Coc.nvim + Clangd搞定嵌入式开发,解决交叉编译链头文件索引的终极脚本
  • Elasticsearch Python Client:官方出品,专治搜索对接的脏活
  • 期末论文复习双重压力?百考通AI帮你高效搞定课业写作难题
  • 智能锡膏柜选购亲测分享:技术好的厂家推荐
  • 2026扇形淋浴房技术解析:宜宾卫生间隔断品牌推荐/宜宾卫生间隔断定制/宜宾淋浴房品牌推荐/材质与空间适配全推荐 - 优质品牌商家
  • 鸿蒙6.0应用开发——网络状态管理
  • 2026年Q2四川地区优秀管理体系认证咨询机构排行 - 优质品牌商家
  • 避开这些坑!用立创EDA手动拼板PCB的完整流程与注意事项
  • 高效空气过滤器哪家好 2026年市场选择指南 - 品牌排行榜
  • LeetCode 2161.根据给定数字划分数组:双指针(O(1)但非源地操作)
  • 鸿蒙原生 ArkTS:margin 溢出、Row 弹性分配与 alignItems 的交互
  • 告别命令行!在Docker Dashboard里点点鼠标就能管理你的Mac版SQL Server
  • 告别截图!MapChart遗传图谱高清导出与个性化样式进阶教程
  • 电商物流避坑指南:这8个快递查询痛点,你遇到过几个?
  • 市面上正规的雾森系统厂家哪家可靠
  • 数据库(基础):
  • 响应式编程:map与flatMap实战解析
  • 2026年评价高的质量管理体系认证top5机构盘点:成都iso27017认证/成都iso27701认证/实力盘点 - 优质品牌商家
  • 大模型应用专家,做好随时涨薪的准备吧~
  • 2026波纹补偿器推荐榜:河南压盖式松套伸缩器/河南双法兰传力伸缩器/河南双法兰限位伸缩器/适配多场景耐腐蚀需求 - 优质品牌商家
  • 从实验室到机柜:1553B总线‘短截线’长度选择的实战避坑指南(直接耦合 vs 间接耦合详解)