1. 题目背景与需求分析
这道PAT乙级1015题"德才论"源于宋代史学家司马光的《资治通鉴》中的著名论述。题目要求我们根据考生的德才分数,按照特定规则进行排序录取。这种将传统文化与现代编程结合的题目设计,在算法竞赛中颇具特色。
核心需求可以分解为三个层次:
- 资格筛选:德分和才分均不低于L的考生才有资格进入后续排序
- 分类规则:将合格考生分为四类(圣人、君子、愚人、小人)
- 排序规则:同类考生按总分降序,总分相同按德分降序,德分相同按准考证号升序
2. 数据结构设计与分类逻辑
2.1 考生信息存储
使用结构体数组是处理这类多属性数据的典型选择:
c复制typedef struct {
int id; // 准考证号(8位)
int de; // 德分
int cai; // 才分
int sum; // 总分(预计算存储)
int level; // 类别标记
} Stu;
预存sum字段虽然会占用额外空间,但能提升排序效率。在实际工程中,这种空间换时间的trade-off很常见。
2.2 四类考生判定标准
分类逻辑需要严格遵循题目要求:
- 圣人(level=1):德分≥H且才分≥H
- 君子(level=2):德分≥H但才分<H
- 愚人(level=3):德分<H且才分<H但德分≥才分
- 小人(level=4):其他合格考生
特别注意level=3的条件组合:必须同时满足三个条件:
- 德分<H
- 才分<H
- 德分≥才分
3. 核心算法实现
3.1 快速排序与比较函数
使用标准库的qsort函数进行排序,关键在于正确实现比较函数:
c复制int cmp(const void *a, const void *b) {
Stu *s1 = (Stu *)a;
Stu *s2 = (Stu *)b;
if (s1->level != s2->level) // 第一优先级:类别
return s1->level - s2->level;
else if (s1->sum != s2->sum) // 第二优先级:总分
return s2->sum - s1->sum;
else if (s1->de != s2->de) // 第三优先级:德分
return s2->de - s1->de;
else // 最后:准考证号
return s1->id - s2->id;
}
比较函数的实现要点:
- 返回值规则:当a应排在b前时返回负值
- 优先级顺序:类别→总分→德分→准考证号
- 注意降序/升序:总分和德分是降序,准考证号是升序
3.2 输入处理与分类
主程序的输入处理流程:
c复制for (int i = 0; i < n; ++i) {
scanf("%d %d %d", &id, &de, &cai);
// 资格筛选
if (de < l || cai < l) continue;
// 存储基本信息
stu[m].id = id;
stu[m].de = de;
stu[m].cai = cai;
stu[m].sum = de + cai;
// 分类逻辑
if (de >= h && cai >= h) {
stu[m].level = 1;
} else if (de >= h) {
stu[m].level = 2;
} else if (de >= cai) {
stu[m].level = 3;
} else {
stu[m].level = 4;
}
m++; // 合格考生计数
}
4. 性能优化与边界情况
4.1 时间复杂度分析
算法主要时间消耗在:
- 输入处理:O(N)
- 快速排序:O(MlogM),其中M是合格考生数
- 输出:O(M)
由于N≤10^5,使用qsort的O(MlogM)复杂度完全可接受。如果N更大(如10^6以上),可能需要考虑更优化的排序策略。
4.2 常见错误与调试技巧
-
分类逻辑错误:最容易出错的是level=3的条件判断,必须确保三个条件同时满足
- 错误示例:
else if (de >= cai)会错误包含德分≥H的考生
- 错误示例:
-
排序顺序错误:注意不同字段的排序方向
- 总分和德分是降序,而准考证号是升序
-
输入输出格式:
- 准考证号是8位整数,要用
%08d格式输出吗?题目没有明确要求,按原样输出即可 - 第一行输出合格人数M,之后输出M行考生信息
- 准考证号是8位整数,要用
-
内存分配:
- 题目给出N≤10^5,栈上分配大数组可能溢出
- 解决方案:定义为全局变量(如示例代码),或动态分配
5. 完整代码实现
c复制#include <stdio.h>
#include <stdlib.h>
#define MAXN 100010
typedef struct {
int id; // 准考证号
int de; // 德分
int cai; // 才分
int sum; // 总分
int level; // 类别
} Stu;
Stu stu[MAXN];
int n, l, h; // 总人数,及格线,优秀线
int m = 0; // 合格人数
int cmp(const void *a, const void *b) {
Stu *s1 = (Stu *)a;
Stu *s2 = (Stu *)b;
if (s1->level != s2->level) // 类别优先
return s1->level - s2->level;
else if (s1->sum != s2->sum) // 总分降序
return s2->sum - s1->sum;
else if (s1->de != s2->de) // 德分降序
return s2->de - s1->de;
else // 准考证号升序
return s1->id - s2->id;
}
int main() {
// 读取输入参数
if (scanf("%d %d %d", &n, &l, &h) != 3) return 0;
// 处理每个考生
for (int i = 0; i < n; ++i) {
int id, de, cai;
scanf("%d %d %d", &id, &de, &cai);
// 资格筛选
if (de < l || cai < l) continue;
// 存储信息
stu[m].id = id;
stu[m].de = de;
stu[m].cai = cai;
stu[m].sum = de + cai;
// 分类
if (de >= h && cai >= h) {
stu[m].level = 1;
} else if (de >= h) {
stu[m].level = 2;
} else if (de >= cai) {
stu[m].level = 3;
} else {
stu[m].level = 4;
}
m++;
}
// 排序
qsort(stu, m, sizeof(Stu), cmp);
// 输出结果
printf("%d\n", m);
for (int i = 0; i < m; ++i) {
printf("%d %d %d\n", stu[i].id, stu[i].de, stu[i].cai);
}
return 0;
}
6. 测试用例与验证
6.1 样例输入测试
使用题目提供的样例输入:
code复制14 60 80
10000001 64 90
10000002 90 60
... [其余输入省略]
验证输出是否与预期一致,特别注意:
- 合格人数是否正确(样例中应为12人)
- 分类顺序是否正确(圣人→君子→愚人→小人)
- 同类考生排序是否正确(总分→德分→准考证号)
6.2 边界情况测试
-
所有考生都不合格:
- 输入:
3 80 90+ 三个德/才分<80的考生 - 预期输出:
0
- 输入:
-
所有考生都是圣人:
- 输入:
2 60 70+ 两个德/才分≥70的考生 - 预期输出:
2+ 两个考生按总分排序
- 输入:
-
分数相同的情况:
- 设计多个总分相同、德分相同的情况,验证准考证号排序
7. 算法扩展思考
如果题目要求变化,可以考虑以下扩展方向:
- 多级分类:增加更多分类层级(如增加"德才俱佳"、"德才平庸"等)
- 动态权重:德分和才分按不同权重计算总分
- 大数据处理:当N极大时(如10^7),可能需要:
- 使用更高效的排序算法(如外部排序)
- 并行处理分类和排序
- 使用更紧凑的数据结构节省内存
在实际工程应用中,类似的分类排序问题很常见,比如:
- 电商平台的商品排序(按类别、评分、销量等)
- 招聘系统的人才筛选(按学历、技能、经验等)
- 学校的学生成绩处理(按班级、总分、单科成绩等)
理解这类问题的解决模式,能够帮助我们快速处理实际开发中的各种排序需求。