HDU 考试排名 题解 by Aze

G - 考试排名 题解 by Aze

1. 要点

  • 结构体 struct 数组的应用

  • 排序 的应用

  • 多重条件排序

  • %-10s 格式化输出

  • 比较复杂的输入处理

2. 前置知识

  • 结构体数组

    • struct的声明,使用

    • 跟数组的区别

  • 冒泡排序(sort也行,我用的快排但思路不太好讲)

3.题目

C++编程考试使用的实时提交系统,具有即时获得成绩排名的特点。它的功能是怎么实现的呢?

我们做好了题目的解答,提交之后,要么“AC”,要么错误,不管怎样错法,总是给你记上一笔,表明你曾经有过一次错误提交,因而当你一旦提交该题“AC”后,就要与你算一算帐了,总共该题错误提交了几回。

虽然你在题数上,大步地跃上了一个台阶,但是在耗时上要摊上你共花去的时间。

特别是,曾经有过的错误提交,每次都要摊上一定的单位时间分。

这样一来,你在做出的题数上,可能领先别人很多,但是,在做出同样题数的人群中,你可能会在耗时上处于排名的劣势。

 

例如:

某次考试一共8题(A,B,C,D,E,F,G,H),每个人做的题都在对应的题号下有个数量标记,负数表示该学生在该题上有过的错误提交次数,但到现在还没有AC,正数表示AC所耗的时间,如果正数a跟上一对括号,里面有个整数b,那就表示该学生提交该题AC了,耗去了时间a,同时,曾经错误提交了b次,因此对于下述输入数据:

若每次错误提交的罚分为20分,则其排名从高到低应该是这样的: Josephus 5 376 John 4 284 Alice 4 352 Smith 3 167 Bob 2 325 Bush 0 0

input:

8 20
Smith     -1    -16 8   0   0   120 39  0
John      116   -2  11  0   0   82  55(1)   0
Josephus  72(3) 126 10  -3  0   47  21(2)   -2
Bush      0 -1  -8  0   0   0   0   0
Alice     -2    67(2)   13  -1  0   133 79(1)   -1
Bob   0 0   57(5)   0   0   168 -7  0

output:

Josephus    5  376
John        4  284
Alice       4  352
Smith       3  167
Bob         2  325
Bush        0    0

4.分析

输出什么结果?

每个人根据 AC题目数 和 时间 进行排名,输出排名列表

既然我们需要进行排序之后再输出,就必须设计一个数据结构来存储这些信息

并且每个人的 AC题目数 和 时间 都是要跟个人绑定的

所以,struct 是最优选择(大概)

struct student{
    char name[11]; // 题上说最长11个字符
    int AC = 0;
    int time = 0; // 直接初始化为0
}
student stu[100020]; // 创建含100020个结构体的数组

数据结构选好之后,开始考虑如何把数据存进去

第一行 n,m 没问题直接读,n 代表题数,m 代表罚时,也就是说,人数是不知道有多少的

所以 用 while(scanf()) 来读每一行

因为后面题数是跟 n 相关,得用 for 计次循环

结合一下,先用while读名字,再用for读成绩

每次循环读入一个人的成绩

while(scanf("%s",&stu[i].name) != EOF)
{
    for(int j = 0; j < n; j++)
    {
        
    }
    i++; // ++后下次循环 就读入 下一个人的成绩
}

接着来看怎么读成绩,有三种情况

  • 正数:一次作对,AC++

  • 负数:没做出来

  • 正数+括号:有做错的,罚时,AC++

那么time是怎么计算的呢?

抓一个分析分析:

Smith     -1    -16 8   0   0   120 39  0
//做出来三道题,总时间为 120 + 39 + 8 = 167
//所以很明显,如果没做出来是不计入时间的 

既然如此,当读入一个负数时直接跳过循环,去读下一个数就好

int score;
scanf("%d",&score);
if(score < 0) continue;

然后是处理 正数+括号,因为scanf("%d")碰到非数字会停下,所以读入后可以接 getchar()判断后一个字符是空格还是左括号(,是括号就特殊处理,不然就直接继续循环

int score;
scanf("%d",&score);
if(score < 0) continue;
if(getchar() == '(') // 判断是否有左括号
{
    //.....罚时计算
}
gerchar(); // 吸收右括号

数据是这么读,然后将其处理一下再存入

int score;
scanf("%d",&score);
if(score <= 0) continue;
stu[i].time += score;
if(getchar() == '(') // 判断是否有左括号
{
    int failed;
    scanf("%d",&failed);
    stu[i].time += failed * m; // 加上罚时
}
gerchar(); // 吸收右括号
stu[i].AC++; // 如果能运行到这说明已经不是负数或者0,这题他整出来了

结合一下

int i = 0;
while(scanf("%s",&stu[i].name) != EOF)
{
	for(int j = 0; j < n; j++)
    {
        int score;
        scanf("%d",&score);
        if(score <= 0) continue;
        stu[i].time += score;
        if(getchar() == '(') // 判断是否有左括号
        {
            int failed;
            scanf("%d",&failed); 
            stu[i].time += failed * m; // 加上罚时
        }
        gerchar(); // 吸收右括号
        stu[i].AC++; // 如果能运行到这说明已经不是负数或者0,这题他整出来了
    }
    i++; // ++后进入下次循环 就读入 下一个人的成绩
}

好耶!数据已经完美存进来了,接下来是将其排序,输出

实时排名显然先按AC题数的多少排,多的在前,再按时间分的多少排,少的在前,如果凑巧前两者都相等,则按名字的字典序排,小的在前。

所以是按以下条件排

  • AC题数

    • 当AC题数相等时,按时间排(用时越少排名越高)

      • 当时间也相等时,按名字字典序排

众所周知,显而易见,懂得都懂

冒泡排序的判断条件长这样

if(a[j - 1] < a[j])
{
	//....
}

很好!那么怎样在括号里多塞几个判断条件 而且 还是递进的判断呢

用 && || ! 肯定 8 行,这都是同时判断

那咋办嘛,只能用函数了

bool check(student &a, student &b) // 从后往前
{
	if(a.AC < b.AC) return true; // 如果b的AC数大于a的AC数就将他们交换位置
    else if (a.AC == b.AC)
    {
        if(a.time > b.time) return true; // 同理交换
        else if(a.time == b.time)
        {
            if(strcmp(a.name,b.name) > 0) return true; // 判断字典序,strcmp自己网上搜搜
        }
    }
    return false; // 如果三个 返回true的都没执行,那说明肯定 a 该排 b 前面
}

在判断中替换一下

if(check(stu[j - 1], stu[j])) // 如果函数返回true说明b该排在a前面
{
    student temp = stu[j - 1]; // 交换
    stu[j - 1] = stu[j];
    stu[j] = temp;
}

排序就不多说了,直接套模板

i--; // 注意while循环结尾时i会多++,减去1之后才是总人数
for(int q = 0; q <= i; q++)
{
    for(int j = i; j > q; j--)
    {
        if(check(stu[j - 1], stu[j])) // 如果函数返回true说明b该排在a前面
        {
            student temp = stu[j - 1]; // 交换
            stu[j - 1] = stu[j];
            stu[j] = temp;
        }
    }
}

结束!排序完成,剩下一个输出

每个学生占一行,输出名字(10个字符宽),做出的题数(2个字符宽,右对齐)和时间分(4个字符宽,右对齐)。名字、题数和时间分相互之间有一个空格。

字符宽的话就 %2d 就行,左右对齐则可以加 - + 号,比如 %-10s 左对齐输出10个字符宽的名字

printf("%-10s %2d %4d\n",stu[i].name,stu[i].AC,stu[i].time);

讲完!

5. 完整代码

#include <stdio.h>
#include <string.h> // 用 strcmp 需要
const int MAX = 100020;
struct student {
    int AC = 0;
    char name[11];
    int time = 0;
};
student stu[MAX];
​
bool check(student& a, student& b) // 从后往前
{
    if (a.AC < b.AC) return true; // 如果b的AC数大于a的AC数就将他们交换位置
    else if (a.AC == b.AC)
    {
        if (a.time > b.time) return true; // 同理交换
        else if (a.time == b.time)
        {
            if (strcmp(a.name, b.name) > 0) return true; // 判断字典序,strcmp自己网上搜搜
        }
    }
    return false; // 如果三个 返回true的都没执行,那说明肯定 a 该排 b 前面
}
​
int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    int i = 0;
    while (scanf("%s", &stu[i].name) != EOF && stu[i].name[0] != '0')
    {
        for (int j = 0; j < n; j++)
        {
            int num;
            scanf("%d", &num);
​
            if (num <= 0)    continue;
            else stu[i].time += num;
​
            if (getchar() == '(')    // 处理括号
            {
                scanf("%d", &num);
                stu[i].time += num * m;
                getchar();
            }
​
            stu[i].AC++;
        }
        i++;
    }   
    i--; // 注意while循环结尾时i会多++,减去1之后才是最后一个人
    for (int q = 0; q <= i; q++) 
    {
        for (int j = i ; j > q; j--)
        {
            if (check(stu[j - 1], stu[j])) // 如果函数返回true说明b该排在a前面
            {
                student temp = stu[j - 1]; // 交换
                stu[j - 1] = stu[j];
                stu[j] = temp;
            }
        }
    }
​
    for (int j = 0; j <= i; j++)   // 输出
    {
        printf("%-10s %2d %4d\n", stu[j].name, stu[j].AC, stu[j].time);
    }
    return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值