1015 德才论

1015 德才论 (25 分)

题意描述:

宋代史学家司马光在《资治通鉴》中有一段著名的“德才论”:“是故才德全尽谓之圣人,才德兼亡谓之愚人,德胜才谓之君子,才胜德谓之小人。凡取人之术,苟不得圣人,君子而与之,与其得小人,不若得愚人。”

现给出一批考生的德才分数,请根据司马光的理论给出录取排名。

输入格式:
输入第一行给出 3 个正整数,分别为:N(≤10^​5​​ ),即考生总数;L(≥60),为录取最低分数线,即德分和才分均不低于 L 的考生才有资格被考虑录取;H(<100),为优先录取线——德分和才分均不低于此线的被定义为“才德全尽”,此类考生按德才总分从高到低排序;才分不到但德分到线的一类考生属于“德胜才”,也按总分排序,但排在第一类考生之后;德才分均低于 H,但是德分不低于才分的考生属于“才德兼亡”但尚有“德胜才”者,按总分排序,但排在第二类考生之后;其他达到最低线 L 的考生也按总分排序,但排在第三类考生之后。

随后 N 行,每行给出一位考生的信息,包括:准考证号 德分 才分,其中准考证号为 8 位整数,德才分为区间 [0, 100] 内的整数。数字间以空格分隔。

输出格式:
输出第一行首先给出达到最低分数线的考生人数 M,随后 M 行,每行按照输入格式输出一位考生的信息,考生按输入中说明的规则从高到低排序。当某类考生中有多人总分相同时,按其德分降序排列;若德分也并列,则按准考证号的升序输出。

输入样例:

14 60 80
10000001 64 90
10000002 90 60
10000011 85 80
10000003 85 80
10000004 80 85
10000005 82 77
10000006 83 76
10000007 90 78
10000008 75 79
10000009 59 90
10000010 88 45
10000012 80 100
10000013 90 99
10000014 66 60

输出样例:

12
10000013 90 99
10000012 80 100
10000003 85 80
10000011 85 80
10000004 80 85
10000007 90 78
10000006 83 76
10000005 82 77
10000002 90 60
10000014 66 60
10000008 75 79
10000001 64 90

解题思路:

根据题意 , 本题实质上是对一个列表(数组)进行排序。只是排序的规则有些复杂。我们可以自己实现一个排序算法(冒泡排序,快速排序,堆排序等等),也可是使用各种库中的排序函数。使用库中的排序函数需要我们写一个决定两个元素之间的 顺序 的函数。其实这正是一个常见的场景,排序的操作是相同的,不同的是不同场景下决定两个元素之间顺序的逻辑。


代码:
第一份代码: Python实现冒泡排序,三个测试点超时。

def main():
    temp = [int(x) for x in input().split()]
    # 接收输入的三个 整数 为一个整数列表 temp
    global L, H
    # python 中的global关键字用于声明全局变量,全局变量可以在整个python文件的范围内被访问到。
    #由于我们需要在主程序中输入L 和 H, 在决定考生类别的函数中也需要访问 L 和 H,所以设置 L H 为全局变量。
    N = temp[0]
    L = temp[1]
    H = temp[2]
    # 使用和 题目描述中相同的变量名更方便,不易出错。
    data = []
    # 用于存储考生的数据,每个考生的数据用一个列表存储,data就是一个列表的列表。
    for x in range(N):
        temp = [int(x) for x in input().split()]
        # temp 暂时存储考生的学号, 德分,才分
        if temp[1] >= L and temp[2] >= L:
            # 德分 和 才分 都 "合格" 的考生才参与 录取
            data.append(temp)
    bubble_sort(data)
    # 对入围的考生使用冒泡排序算法排序
    print(len(data))
    # 输出 入围 的考生总数
    for x in data:
        print(x[0], x[1], x[2])
        # python的print()函数会给逗号分隔的两项之间自动加上空格
    return


def bubble_sort(data):
    # 冒泡排序,对考生列表进行排序
    for x in range(0, len(data)):
        # 每一趟,将一个排名靠前的考生 提到 列表前面来。即第一趟循环找到排名第一的考生并把她放到data[0]的位置,以此类推。
        for y in range(x + 1, len(data)):
            if is_swap(data[x], data[y]):
                # 如果两个元素需要交换位置
                data[x], data[y] = data[y], data[x]
                # 交换两个元素的位置,这是python中交换两个元素的一种特殊的写法
    return
    # 由于我们直接更改了传进来的列表,所以这里并不需要返回值


def is_swap(student_a, student_b):
    # 判断两个元素 是否需要交换位置。传入的参数是两个列表,分别存储了两位考生的信息。
    # 注意参数传入的顺序,student_a 是位于列表中靠前的位置的。
    if get_level(student_a) > get_level(student_b):
        # 如果 a 的类别数值大(不被优先录取),需要交换。
        # 类别有1 2 3 4 5(5是未入围),录取优先级依次降低。这样设置是为了契合题目中对考生类别的描述。
        return True
    elif get_level(student_a) < get_level(student_b):
        # a 的 类别数值小,不需交换
        return False
    else:
        # 同一类别的考生
        if student_a[1] + student_a[2] < student_b[1] + student_b[2]:
            # 如果 a 的总分 小, 需要交换
            return True
        elif student_a[1] + student_a[2] > student_b[1] + student_b[2]:
            # 如果 a 的总分 大, 不需要交换
            return False
        else:
            #同一类别且总分相同的考生 
            if student_a[1] < student_b[1]:
                # 若 a 的德分 更低,需要交换
                return True
            elif student_a[1] > student_b[1]:
                # 若 a 的德分 更高,不需要交换
                return False
            else:
                #同一类别,总分相同德分也相同的考生。若 a 的学号较大,交换。  
                return student_a[0] > student_b[0]


def get_level(student):
    # 返回一个考生的类别
    if student[1] < L and student[2] < L:
        # 未入围考生
        return 5
    else:
        if student[1] >= H and student[2] >= H:
            # 一类考生
            return 1
        elif student[1] >= H and student[2] < H:
            # 二类考生
            return 2
        elif student[1] < H and student[2] < H and student[1] >= student[2]:
            # 三类考生
            return 3
        else:
            # 四类考生
            return 4


if __name__ == '__main__':
    main()

第二份代码: 使用list.sort( )实现,三个测试点超时。

from functools import cmp_to_key
# python3的列表的sort函数取消了cmp参数,需要使用cmp_to_key将其转换为一个key参数传入

def main():
    temp = [int(x) for x in input().split()]
    # 接收输入的三个 整数 为一个整数列表 temp
    global L, H
    # python 中的global关键字用于声明全局变量,全局变量可以在整个python文件的范围内被访问到。
    L = temp[1]
    H = temp[2]
    #由于我们需要在主程序中输入L 和 H, 在决定考生类别的函数中也需要访问 L 和 H,所以设置 L H 为全局变量。

    data = []
    for x in range(temp[0]):
        temp = [int(x) for x in input().split()]
        if temp[1] >= L and temp[2] >= L:
            # 存储 入围 的考生信息
            data.append(temp)
    data.sort(key=cmp_to_key(compare))
    # 使用列表的sort函数对其进行排序,注意cmp_to_key的用法,compare是我们自己定义的一个判断的函数。
    print(len(data))
    # 输出 入围 的考生数量
    for x in range(len(data)):
        print(' '.join([str(y) for y in data[x]]))
        # 也可以先将答案拼接成字符串再一起输出


def compare(a, b):
    # compare是我们定义用来对列表进行排序的函数,传入的两个参数都是待排序列表的元素,且 a 位于 b 的前面。
    a_level = get_level(a)
    b_level = get_level(b)
    # 获取两个考生的类别
    if a_level == b_level:
        # 类别相同的情况
        if b[1] + b[2] != a[1] + a[2]:
            #若a的总分数值较高(优先级较高),表达式为负,则不交换位置。
            return b[1] + b[2] - (a[1] + a[2])
        else:
            # 同类别考生总分相同的情况
            if a[1] != b[1]:
                return b[1] - a[1]
                #若a的德数值较高(优先级较高),表达式为负,则不交换位置。
            else:
                # 德分也相同
                return a[0] - b[0]
                #若a的学号数值较低(优先级较高),表达式为负,则不交换位置。
    else:
        return a_level - b_level
        # 对于python列表的sort函数传入的 比较函数,如果返回值为正就交换元素的位置。若a的类别数值较高(优先级较低),表达式为正,则交换位置。


def get_level(a):

    if a[1] >= L and a[2] >= L:
        # 达到基本录取条件
        if a[1] >= H and a[2] >= H:
            # 才德全尽,一类考生
            return 1
        else:
            if a[1] >= H and a[2] < H:
                # 德胜才,二类考生
                return 2
            else:
                if a[1] < H and a[2] < H and a[1] >= a[2]:
                    # 才德兼亡”但尚有“德胜才”者,三类考生
                    return 3
                else:
                    # 其他达到最低线 L 的考生,4类考生
                    return 4
    else:
        # 未入围考生
        return 5


if __name__ == '__main__':
    main()

第三份代码:使用C++algorithm 库中的sort函数实现,通过所有测试点。

#include <stdio.h> 
#include <algorithm>
/* 需要使用algorithm库中的sort函数,algorithm为C++专有的库,(C语言没有)。
所以虽然没有使用C++语言的特性,我们仍然写成c++代码。*/
using namespace std;
// C++程序需要声明命名空间
struct info{
	int stu_id;
	// 学号
	int d_score;
	// 德分
	int c_score;
	// 才分
	int level;
	// 考生的类别
	int total_score;
	/* 总分,之所以将类别和总分直接放到结构体中是为了减少重复计算。
	如果一个元素在排序的过程中要与多个元素比较,那么它的类别和总分就会被计算多次。*/
	
}students[100000 + 1];
//定义一个结构体类型的数组 student, 数组长度 为10^5 + 1,因为题目中输入的上限是 10^5 个考生的信息, 加一是为了防止数组越界访问。
int N, L, H;
// 将 NLH 定义为全局变量
bool compare(info student_a, info student_b){
	// 参数为两个info 结构体型的变量,返回值是布尔类型,返回True时,不交换。
	// 同样注意传入的参数在数组中的顺序和返回值 之间的对应关系。
    if (student_a.level == student_b.level){
    	if (student_a.total_score != student_b.total_score){
    		return student_a.total_score > student_b.total_score;
    	} else{
    		if (student_a.d_score != student_b.d_score){
    			return student_a.d_score > student_b.d_score;
    		}else{
                return student_a.stu_id < student_b.stu_id; 
    		}
    	}
    }else{
    	return student_a.level < student_b.level;
    }
      	
}

int get_level(int d_score, int c_score){
    // 参数是一个考生的德分和才分,根据规则返回类别(整数)
	 if (d_score >= L && c_score >= L){
	 	if (d_score >= H && c_score >= H){
	 		return 1;
	 	}else{
        	if (d_score >= H && c_score < H){
        		return 2;
        	}else{
        		if (d_score < H && c_score < H && d_score >= c_score){
        			return 3;
        		}else{	
                    return 4;
				}
            }
        }
	 }else{
	 	return 5;
	 }
}

int main(){
    scanf("%d %d %d", &N, &L, &H);
    // 输入 NLH 
	int id, d_score, c_score;
	// 临时存储考生信息
	int count = 0;
	// 累计入围考生数量
	for (int i=0; i<N; ++i){
		scanf("%d %d %d", &id, &d_score, &c_score);
		// 输入考生信息
		if (d_score >= L && c_score >= L){
		  // 入围考生
			students[count].stu_id = id;
			students[count].d_score = d_score;
			students[count].c_score = c_score; 
			students[count].total_score = d_score + c_score;
		    students[count].level = get_level(d_score, c_score);
		    // 只计算一次考生的类别和总分
		    count ++;
		}
	}
	sort(students, students+count, compare);
	// 排序,传入的三个参数分别是待排序的数组的起始地址和终止地址,用于判断数组中的两个元素是否需要交换的函数。
	printf("%d\n", count);
	//输入考生总数
	for(int i=0;i<count; ++i){
		printf("%d %d %d\n", students[i].stu_id, students[i].d_score, students[i].c_score);
	}
	//输出排序结果
	return 0;
}

第四份程序,在第二份程序的基础上减少了总分和类别的重复计算,仍然有两个测试点超时(测试点3,4)

from functools import cmp_to_key


def main():
    temp = [int(x) for x in input().split()]
    global L, H
    L = temp[1]
    H = temp[2]
    data = []
    for x in range(temp[0]):
        temp = [int(x) for x in input().split()]
        if temp[1] >= L and temp[2] >= L:
            temp.append(get_level(temp))
            temp.append(temp[1] + temp[2])
            data.append(temp)
    data.sort(key=cmp_to_key(compare))
    print(len(data))
    for x in range(len(data)):
        print(' '.join([str(data[x][y]) for y in range(3)]))


def compare(a, b):
    a_level = a[3]
    b_level = b[3]
    if a_level == b_level:
        if b[4] != a[4]:
            return b[4] - a[4]
        else:
            # 同类别考生总分相同的情况
            if a[1] != b[1]:
                return b[1] - a[1]
            else:
                # 德分也相同
                return a[0] - b[0]
    else:
        return a_level - b_level


def get_level(a):

    if a[1] >= L and a[2] >= L:
        # 达到基本录取条件
        if a[1] >= H and a[2] >= H:
            # 才德全尽
            return 1
        else:
            if a[1] >= H and a[2] < H:
                # 德胜才
                return 2
            else:
                if a[1] < H and a[2] < H and a[1] >= a[2]:
                    # 才德兼亡”但尚有“德胜才”者
                    return 3
                else:
                    # 其他达到最低线 L 的考生
                    return 4
    else:
        return 5


if __name__ == '__main__':
    main()


易错点:

  • 使用Python实现会有三个超时的测试点,而且在网上还没有不超时的python程序出现,可能是由于python的性能限制所致。建议PAT网站适当放松对python 版本代码的时间限制。
  • 注意题目描述中 德分 和 才分 均 < L的考生会被先排除在外,接下来对考生类别的描述不考虑这些考生。

总结:

  • 自顶向下,逐步细化和模块化是处理复杂逻辑的一个可靠的方法。
  • 主函数一定不能写的太长。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值