最近帮一个学弟排查洛谷P1068 [NOIP 2009 普及组] 分数线划定,他排序写了,样例也过了,交上去却一直不是满分。拿去对照之后发现,问题几乎都出在同一个地方:他把“m * 1.5 向下取整”当成了最终要输出的人数。这个理解看似只差一步,实际上已经偏离了题目规则,最后名单自然也不对。
P1068是一道很适合用来练结构体排序和边界条件的普及组题目,也是CSP-J备考过程中很多人会反复遇到的经典模型。只看标题,会觉得这是一道“排个序、输出几个人”的水题;但真正动手写过之后,你会发现它对读题能力的要求比排序本身更高。这篇文章会从题目规则拆解开始,讲清楚分数线与录取人数的关系,再给出C++和Python两种可提交的完整实现,最后把几个容易让人卡住的边界点一起捋一遍。
1. 1.5倍入围线的本质:为什么线以第k名为准,人数却可能更多
1.1 原题要你做的事到底是什么
先把题面转换成人话。大概意思是:有n名选手,原本计划录取m人。由于报名人数相对较多,实际流程并不直接录取前m名,而是把计划人数乘以1.5后向下取整,得到一个新的位置k。
接下来,按成绩从高到低排序,如果成绩相同,就按报名号从小到大排序。排序结束后,取第k名选手的成绩作为“面试分数线”。
到这里都很直观,麻烦的是下一句:所有成绩不低于这个分数线的选手,全部进入面试。
这句话意味着,最终人数并不一定等于k,而是可能大于k。因为第k名选手的分数,可能和第k+1名、第k+2名完全一样。只要这些人都达到分数线,按照同分规则就都应该被输出。
很多第一次写这题的选手会形成一种直觉:既然是“录取m人”,那我排序之后取前m个不就行了?如果题目说的是直接录取前m人,那确实没问题。但P1068用的是“先画一条分数线,过线的人全部入围”的规则,所以不能直接用m,也不能直接用k去截断输出。
1.2 一个边界序列直接说明问题
假设当前输入是n=6,m=3。先算一下位置:m乘1.5等于4.5,向下取整后,k=4。
如果按成绩排序后出现下面这个序列:
| 排序位置 | 笔试成绩 | 是否进入面试 |
|---|---|---|
| 1 | 95 | 进入面试 |
| 2 | 90 | 进入面试 |
| 3 | 88 | 进入面试 |
| 4 | 80 | 进入面试 |
| 5 |
