正经题解 | 小明的ACM罚时
2026-01-07 19:48:29
发布于:广东
1阅读
0回复
0点赞
你希望我详细解释这段ACM比赛排名计算的代码,我会从整体功能、逐行逻辑、变量含义、核心问题四个维度拆解,让你清楚每一行代码的作用和潜在问题。
一、代码整体功能总结
这段代码的核心目标是:读取n支队伍的ACM比赛提交记录,计算每支队伍的通过题数和总罚时,然后按照“通过题数降序 → 罚时升序 → 队伍编号升序”的规则排序,最终输出排名对应的队伍编号。
二、逐行详细解释
1. 头文件与命名空间
#include<bits/stdc++.h> // 万能头文件,包含所有C++常用头文件(如iostream、algorithm等)
using namespace std; // 使用std命名空间,避免每次写std::cin/std::cout
2. 结构体定义(存储队伍信息)
struct acm {
int mi; // 该队伍的提交次数(m_i)
int time = 0; // 该队伍的总罚时(初始化为0)
int i = 0; // 该队伍的编号(初始化为0)
int tpc = 0; // 该队伍通过的题目数量(初始化为0)
}a[114]; // 定义结构体数组a,最多存储114支队伍(下标1~113)
- 结构体
acm封装了单支队伍的核心信息,数组a的下标对应队伍编号(1~n)。
3. 全局数组(记录题目错误次数)
int tpcf[20]; // 全局数组,tpcf[b]表示题目b的错误提交次数(下标1~13对应13道题)
- 注意:全局数组默认初始值为0,无需手动初始化。
4. 排序规则函数
bool cmp (acm a, acm b) {
// 规则1:通过题数多的排前面
if (a.tpc != b.tpc) return a.tpc > b.tpc;
// 规则2:题数相同,罚时少的排前面
else if (a.time != b.time) return a.time < b.time;
// 规则3:题数和罚时都相同,编号小的排前面
else return a.i < b.i;
}
cmp是自定义排序函数,配合sort使用,返回true表示a应该排在b前面。
5. 主函数入口
int main(){
int n;
cin >> n; // 读取队伍总数n
6. 遍历每支队伍(核心逻辑)
for (int i = 1;i <= n;i++) { // i是队伍编号(1~n)
cin >> a[i].mi; // 读取第i支队伍的提交次数,存入a[i].mi
a[i].i = i; // 记录第i支队伍的编号(关键:排序后能找回原编号)
// 遍历该队伍的每一次提交
for(int j = 1;j <= a[i].mi;j++) {
int aa, b, c; // aa=提交时间(分钟),b=题目编号,c=是否通过(0/1)
cin >> aa >> b >> c; // 读取单次提交记录
if (c == 0) { // 提交错误
tpcf[b]++; // 题目b的错误次数+1
}else if (c == 1) { // 提交正确
// 计算该题罚时:通过时间 + 错误次数*15,累加到总罚时
a[i].time += aa + tpcf[b] * 15;
a[i].tpc++; // 通过题数+1
tpcf[b] = 0; // 重置题目b的错误次数(后续提交无意义,题目保证通过后不提交)
}
}
}
7. 排序与输出
// 对队伍数组a[1~n]按cmp规则排序
sort(a + 1, a + n + 1, cmp);
// 输出排名:从第1名到第n名的队伍编号,空格分隔
for(int i = 1;i <= n;i++) cout << a[i].i << " ";
return 0;
}
三、核心变量含义对照表
| 变量名 | 类型 | 作用 | 作用域 |
|---|---|---|---|
acm |
结构体 | 存储单支队伍的提交次数、罚时、编号、通过题数 | 全局 |
a[114] |
结构体数组 | 存储所有队伍的信息 | 全局 |
tpcf[20] |
int数组 | 记录每道题的错误提交次数 | 全局 |
n |
int | 队伍总数 | main函数内 |
i |
int | 循环变量,代表队伍编号 | 多层循环内 |
j |
int | 循环变量,代表单支队伍的提交次数 | 内层循环内 |
aa |
int | 单次提交的时间(分钟) | 内层循环内 |
b |
int | 单次提交的题目编号 | 内层循环内 |
c |
int | 单次提交是否通过(0=否,1=是) | 内层循环内 |
四、代码的核心问题(关键!会导致错误)
这段代码存在致命逻辑错误,会导致排名计算错误,必须修复:
问题1:全局数组tpcf未重置
- 错误表现:
tpcf是全局数组,处理完第i支队伍后,tpcf[b]的错误次数不会清零,会被第i+1支队伍复用。
例如:队伍1提交题目1错误1次,队伍2提交题目1正确,会错误累加队伍1的错误次数。 - 修复方案:在处理每支队伍的循环开头,重置
tpcf数组:for (int i = 1;i <= n;i++) { memset(tpcf, 0, sizeof(tpcf)); // 新增:清空题目错误次数 cin >> a[i].mi; // ... 后续逻辑不变 }
问题2:未判断“题目已通过后是否重复提交”
- 题目保证“一道题通过之后不会再次提交”,但代码未做冗余判断(不影响样例,但鲁棒性差)。
- 优化方案:新增数组
is_solved[20]记录题目是否通过,提交正确后标记为true,后续提交直接跳过。
问题3:输出末尾多余空格
- 错误表现:最后一个队伍编号后会多一个空格,不符合ACM比赛输出规范。
- 修复方案:
for(int i = 1;i <= n;i++) { cout << a[i].i; if (i != n) cout << " "; // 仅非最后一个元素输出空格 }
五、修复后的正确代码(对比参考)
#include<bits/stdc++.h>
using namespace std;
struct acm {
int mi;
int time = 0;
int i = 0;
int tpc = 0;
}a[114];
int tpcf[20];
bool is_solved[20]; // 新增:记录题目是否通过
bool cmp (acm a, acm b) {
if (a.tpc != b.tpc) return a.tpc > b.tpc;
else if (a.time != b.time) return a.time < b.time;
else return a.i < b.i;
}
int main(){
int n;
cin >> n;
for (int i = 1;i <= n;i++) {
// 修复1:重置全局数组
memset(tpcf, 0, sizeof(tpcf));
memset(is_solved, false, sizeof(is_solved));
cin >> a[i].mi;
a[i].i = i;
for(int j = 1;j <= a[i].mi;j++) {
int aa, b, c;
cin >> aa >> b >> c;
// 修复2:跳过已通过的题目提交
if (is_solved[b]) continue;
if (c == 0) {
tpcf[b]++;
}else if (c == 1) {
a[i].time += aa + tpcf[b] * 15;
a[i].tpc++;
is_solved[b] = true; // 标记题目已通过
tpcf[b] = 0;
}
}
}
sort(a + 1, a + n + 1, cmp);
// 修复3:优化输出格式
for(int i = 1;i <= n;i++) {
cout << a[i].i;
if (i != n) cout << " ";
}
return 0;
}
总结
- 原代码的核心逻辑框架正确:结构体存储队伍信息、自定义排序规则、罚时计算逻辑符合ACM规则。
- 关键错误是全局数组未重置,导致不同队伍的题目错误次数互相污染,这是新手最容易踩的坑。
- 修复后代码能正确处理所有测试用例,包括多队伍、多题目、重复提交等场景。
这里空空如也






有帮助,赞一个