题解:洛谷 B3968 成绩排序
【题目来源】
洛谷:B3968 [GESP202403 五级] 成绩排序 - 洛谷
【题目描述】
有 名同学,每名同学有语文、数学、英语三科成绩。你需要按如下规则对所有同学的成绩从高到低排序:
- 比较总分,高者靠前;
- 如果总分相同,则比较语文和数学两科总分,高者靠前;
- 如果仍相同,则比较语文和数学两科的最高分,高者靠前;
- 如果仍相同,则二人并列。
你需要输出每位同学的排名,如遇 \(x\) 人并列,则他们排名相同,并留空后面的 \(x-1\) 个名次。例如,有 \(3\) 名同学并列第 \(1\),则后一名同学自动成为第 \(4\) 名。
【输入】
第一行一个整数 \(N\),表示同学的人数。
接下来 \(N\) 行,每行三个非负整数 \(c_i,m_i,e_i\) 分别表示该名同学的语文、数学、英语成绩。
【输出】
输出 \(N\) 行,按输入同学的顺序,输出他们的排名。
注意:请不要按排名输出同学的序号,而是按同学的顺序输出他们各自的排名
【输入样例】
6
140 140 150
140 149 140
148 141 140
141 148 140
145 145 139
0 0 0
【输出样例】
1
3
4
4
2
6
【核心思想】
-
问题分析:给定 \(N\) 名同学的语文、数学、英语成绩,需要按四重优先级排序(总分 → 语数总分 → 语数最高分 → 编号),并处理并列情况(并列者排名相同,后续排名跳过)。最后按输入顺序输出每位同学的排名。这是一个多关键字自定义排序 + 并列排名处理问题。
-
算法选择:
- 结构体封装:将学生成绩、派生指标和排名封装为结构体
- 双重排序:先按成绩规则排序计算排名,再按编号排序恢复输入顺序输出
-
关键步骤:
- 读入数据:读取 \(N\) 和每位同学的 \(c_i, m_i, e_i\)
- 预处理派生指标:
- \(tot = c_i + m_i + e_i\)(总分)
- \(ys = c_i + m_i\)(语数总分)
- \(max\_ys = \max(c_i, m_i)\)(语数最高分)
- 第一次排序(\(cmp\)):按 \(tot \downarrow \to ys \downarrow \to max\_ys \downarrow \to id \uparrow\)
- 计算排名(处理并列):
- \(cnt \leftarrow 1\)
- 遍历排序后的学生:
- 若当前学生与前一名在 \(tot, ys, max\_ys\) 上均相同:\(rk = rk_{prev}\)(并列)
- 否则:\(rk = cnt\)(新排名)
- \(cnt \leftarrow cnt + 1\)
- 第二次排序(\(cmp2\)):按 \(id\) 升序恢复输入顺序
- 输出结果:按 \(id\) 顺序输出 \(rk\)
-
时间/空间复杂度:
- 时间复杂度:\(O(N \log N)\),两次排序
- 空间复杂度:\(O(N)\),存储学生数组
-
多关键字排序与并列处理的核心思想:
- 派生指标预处理:将排序所需的复合指标(语数总分、语数最高分)在读入时计算并存储,避免排序时重复计算
- 并列判定条件:仅比较前三项指标(总分、语数总分、语数最高分),编号仅用于打破平局,不参与并列判定
- 排名计数器的跳跃:\(cnt\) 始终递增,但 \(rk\) 在并列时保持不变,实现"留空后面的 \(x-1\) 个名次"的效果
- 双重排序策略:第一次排序确定排名,第二次排序恢复输入顺序,确保按"输入顺序"输出排名而非按排名输出学生
- 适用于复杂排序规则、并列处理、按原始顺序输出的多关键字排序类问题
【算法标签】
普及- #其他排序
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int n; // 学生人数
// 学生结构体,存储各科成绩和相关信息
struct Stu {
int yw, sx, yy; // 语文、数学、英语成绩
int tot; // 总分
int ys; // 语文+数学总分
int max_ys; // 语文和数学中的最高分
int id; // 学生编号
int rk; // 最终排名
};
Stu stu[10005]; // 学生数组
// 排序比较函数1:用于成绩排序
bool cmp(Stu x, Stu y) {
if (x.tot != y.tot) return x.tot > y.tot; // 规则1:总分高的在前
else if (x.ys != y.ys) return x.ys > y.ys; // 规则2:语文+数学高的在前
else if (x.max_ys != y.max_ys) return x.max_ys > y.max_ys; // 规则3:语文或数学单科高的在前
else return x.id < y.id; // 规则4:编号小的在前
}
// 排序比较函数2:用于按学生编号排序输出
bool cmp2(Stu x, Stu y) {
return x.id < y.id;
}
int main() {
cin >> n; // 输入学生人数
// 输入并处理每个学生的成绩数据
for (int i = 1; i <= n; i++) {
int ci, mi, ei;
cin >> ci >> mi >> ei;
stu[i].id = i; // 记录学生编号
stu[i].yw = ci; // 语文成绩
stu[i].sx = mi; // 数学成绩
stu[i].yy = ei; // 英语成绩
stu[i].ys = ci + mi; // 语文+数学总分
stu[i].max_ys = max(ci, mi); // 语文或数学最高分
stu[i].tot = ci + mi + ei; // 三科总分
}
// 第一次排序:按成绩规则排序
sort(stu + 1, stu + n + 1, cmp);
int cnt = 1; // 排名计数器
// 计算每个学生的实际排名(处理并列情况)
for (int i = 1; i <= n; i++) {
// 检查是否与前一名学生成绩完全相同
if (i > 1 && stu[i].tot == stu[i-1].tot &&
stu[i].ys == stu[i-1].ys &&
stu[i].max_ys == stu[i-1].max_ys) {
stu[i].rk = stu[i-1].rk; // 并列排名
} else {
stu[i].rk = cnt; // 新排名
}
cnt++; // 排名计数器递增
}
// 第二次排序:按学生编号排序,方便输出
sort(stu + 1, stu + n + 1, cmp2);
// 输出每个学生的排名
for (int i = 1; i <= n; i++) {
cout << stu[i].rk << endl;
}
return 0;
}
【运行结果】
6
140 140 150
140 149 140
148 141 140
141 148 140
145 145 139
0 0 0
1
3
4
4
2
6
浙公网安备 33010602011771号