C++ 算法考级专栏
真题分析、矩阵探测、递归回溯与基础语法
🎨 视觉封面【计数排序与初级排序】GESP四级 / CSP-J 题解:luogu-P1271 【深基9.例1】选举学生会
洛谷 P1271【深基9.例1】选举学生会。本题出自《深入浅出程序设计竞赛 - 基础篇》,是学习计数排序(Counting Sort / 桶排序思想)与非比较排序算法的标志性入门经典试题,被标准收录于 CCF GESP 四级认证考纲(初级排序考点)及 CSP-J 普及组核心必做题单。题目核心考点在于突破传统基于比较的 排序思维瓶颈,利用“候选人编号值域小()而选票样本总量极大()”这一关键数据特征,采用频数统计数组实现 线性时间与 极小内存的优化解法。题目难度⭐☆☆☆☆,洛谷难度等级评定为入门。
luogu-P1271 【深基9.例1】选举学生会
🔗 洛谷原题传送门:luogu-P1271 【深基9.例1】选举学生会
题目要求
题目描述
学校正在选举学生会成员,有 名候选人,每名候选人编号分别从 到 ,现在收集到了 张选票,每张选票都写了一个候选人编号。现在想把这些堆积如山的选票按照投票数字从小到大排序。
输入格式
输入 和 以及 个选票上的数字。
输出格式
求出排序后的选票编号。
输入输出样例 #1
样例输入 #1
5 10
2 5 2 2 5 2 2 2 1 2
样例输出 #1
1 2 2 2 2 2 2 2 5 5
说明/提示
对于 的数据,,。
题目深度剖析
1. 数据规模与算法博弈
在算法竞赛与等级考试中,审题的第一步是观察数据规模与值域边界:
- 候选人编号上限:(值域极小,严格落在 之间);
- 选票总数:(数据量极大,达到两百万级)。
针对该数据特征,我们可以对比两种解题路线:
| 算法类型 | 具体方案 | 时间复杂度 | 空间复杂度 | 评价 |
|---|---|---|---|---|
| 传统比较排序 | 将 个数存入数组,调用 std::sort |
次运算 | (需开大小 数组,约 ) | 能够勉强卡过时限,但时空开销偏大,非最优解 |
| 非比较类排序(计数排序) | 开大小为 的桶统计每个候选人的得票频次 | 次加法 | (仅需大小 的数组,约 ) | 最优解,线性时间,极小内存,代码极精炼 |
2. 计数排序(Counting Sort)核心原理
计数排序是一种基于散列(Hash)与频数统计思想的非比较排序算法:
- 建立频数桶:定义全局数组
int cnt[MAXN];,其中cnt[i]表示编号为 的候选人目前一共获得了多少张选票; - 扫描与统计(投票入桶):依次读入 张选票上的候选人编号 ,每读入一个数就执行
cnt[x]++。扫描一遍输入流后,所有候选人的得票总数均已统计完毕; - 有序展开输出(按号放票):按照候选人编号升序从小到大依次枚举 。若候选人 获得了
cnt[i]张票,则连续输出cnt[i]个数字 。
由于我们从编号 开始按顺序遍历到 ,因此输出的所有选票自然而然地满足了由小到大的升序要求,完全避免了元素两两之间的比较与频繁移动!
3. 规范避坑与实现细节
- 避免局部变长数组(VLA):
- 在 C++11 和信奥考纲规范下,严禁在函数内编写形如
int cnt[n + 1];的局部变长数组; - 规范做法是在全局数据段定义常量
const int MAXN = 1005;并声明静态全局数组int cnt[MAXN];,全局数组在程序加载时自动完成零初始化。
- 在 C++11 和信奥考纲规范下,严禁在函数内编写形如
- 输出格式控制:
- 题目样例输出中各个数字以空格分隔。使用布尔标记
bool is_first = true;控制输出:第一个输出的数字前不加空格,后续每个数字前打印一个空格,末尾输出换行符。
- 题目样例输出中各个数字以空格分隔。使用布尔标记
- 标准输入输出流纯粹性:
- 严格遵循竞赛规范,直接读入输入数据,不写多余的防御性读入判断;
- 逻辑自然清晰,适合初学者建立稳固的算法基本功。
完整参考代码 (C++11)
/**
* Problem: luogu-P1271 【深基9.例1】选举学生会
* Algorithm: 计数排序 (Counting Sort) 与初级排序
* Standard: C++11 (CCF GESP 官方大纲规范)
* Author: OneCoder
*/
#include <iostream>
using namespace std;
// 计数数组(桶):由于候选人编号范围是 1 ~ n,且 n <= 999
// 在全局数据区定义静态数组,大小开至 1005,自动零初始化,杜绝函数内使用局部变长数组 (VLA)
const int MAXN = 1005;
int cnt[MAXN];
int main() {
int n, m;
// 读入候选人数量 n 与选票总数 m
cin >> n >> m;
// 循环读入 m 张选票,在对应候选人编号的桶中累加得票数
for (int i = 0; i < m; ++i) {
int vote;
cin >> vote;
cnt[vote]++;
}
// 按照候选人编号从 1 到 n 升序遍历,展开输出所有选票
bool is_first = true; // 用于精准控制数字之间的空格分隔
for (int i = 1; i <= n; ++i) {
// 当候选人 i 仍有剩余票数时,持续输出编号 i
while (cnt[i] > 0) {
if (!is_first) {
cout << " ";
}
cout << i;
is_first = false;
cnt[i]--; // 票数递减
}
}
cout << "\n";
return 0;
}
复杂度深度分析
- 时间复杂度:
- 数据读入与频次累加:遍历读入 张选票并更新计数,耗时 ;
- 结果输出:外层循环遍历 个候选人,内层
while循环的总执行次数恰好等于总票数 ,耗时 ; - 综合时间复杂度为 。在最坏数据规模 时,总操作次数仅约 次,耗时约为 ,极其宽裕地通过时限要求。
- 空间复杂度:
- 全局计数数组大小为 ;
- 综合空间复杂度为 ,在 的海量选票下几乎不占用内存,远优于保存全部选票所需的 (约 )。
总结与同类考点拓展
“选举学生会”是非比较类排序思想的经典启蒙题。解决此类问题的核心启示在于:
- 值域敏感性:当待排序数据的取值范围()远小于数据总量()时,优先考虑计数排序或桶排序;
- 空间换时间:通过数组下标与元素数值的直接映射,将排序的时间下界由比较排序的 突破至线性时间 ;
- 稳定性与扩展:基础计数排序通过前缀和优化还可以实现稳定排序(Stable Sort),广泛应用于基数排序(Radix Sort)的内部子过程。
经典同类推荐练习题:
- luogu-P1059 [NOIP 2006 普及组] 明明的随机数(桶计数去重与排序)
- luogu-P1177 【模板】排序(初级与高级排序综合对比)
- luogu-P1104 生日(初级多关键字排序)
所有代码开源上传至 GitHub:yummy-code 仓库 · GESP 专题站:GESP WIKI
欢迎加入:C++ GESP/CSP 考级答疑群(688906745) 与 Java/Python交流群(982860385),点击可直接加群。
猜你想读 · 相关文章推荐
【结构体与多关键字排序】GESP四级 / CSP-J 题解:luogu-P1093 [NOIP2007 普及组] 奖学金
NOIP 2007 普及组第一题,洛谷 P1093。本题是青少年信息学奥赛与编程等级考试中学习结构体(struct)与多关键字排序(Multi-key Sorting)的标志性入门经典试题,被标准收录于 CCF GESP 四级认证考纲(结构体与排序考点)及 CSP-J 普及组核心必做题单。题目重点考察结构体定义、数据复...
【二维数组与行列双重排序】GESP四级 / CSP-J 题解:luogu-B4580 [GESP202609 四级] 有序网格
CCF GESP 2026年9月认证(第十五次认证)C++ 四级试题,洛谷 B4580。本题严格遵循 CCF GESP 官方大纲规范,重点考察二维数组与行列双重排序。题目逻辑严密,模型典型,是深入理解与掌握信奥核心考点的经典范例。
【顺时针单向递归与双状态递推】GESP四级 / CSP-J 题解:luogu-B4579 [GESP202609 四级] 新汉诺塔
CCF GESP 2026年9月认证(第十五次认证)C++ 四级试题,洛谷 B4579。本题严格遵循 CCF GESP 官方大纲规范,重点考察顺时针单向递归与双状态递推。题目逻辑严密,模型典型,是深入理解与掌握信奥核心考点的经典范例。
OneCoder (lihongzheshuai)
一个中年人的自留地,记录学习 C++、GESP/NOI、Java、Python 与算法架构的心得体会。本站唯一网址:coderli.com