PAT甲级1062、乙级1015 (25分)极简40行C++四个版本任你选!

题目链接:(中英文都有,题目完全等价,代码可通用)

1015 德才论 (25分)

1062 Talent and Virtue (25分)

题意解析:

给出N个学生的 ID、德分、才分,进行综合排序,排序按以下4步优先级进行:

1.先按分类mark等级降序排列(不及格者直接剔除)

分类如下表:(分数为整数,用区间表达)

优先级(数字大优先)分类mark德分(=V)才分(=T)
3圣人[H,100][H,100]
2君子[H,100][L,H)
1愚人[L,H) (德≥才)[L,V]
0小人[L,T) (德<才)[L,100) (易错!)
-1(剔除)不及格[0,L)[0,L)

要特别注意的是!才分优良,但德分不优者算小人。所以建议先剔除不合格者,再按德分是否优良分两大类,再细分。

2.同等级者按总分降序排序

3.同等级同总分者按德分降序排序

4.同分数者按ID升序排序(最后这个是升序!)

算法思路:

  1. 先分类(四类人)后排序。笔者搜索他人的题解发现有些做法是在排序的比较函数中用if/else判断四类人,如此不仅代码冗长,执行排序的效率也较低。应该在录入信息的时候就分类!
  2. 设计结构体(共用体也是特殊的结构体)存储每个学生的信息,并设计其排序函数。
  3. 用系统快排排序(也有的做法是用桶排序等,不仅代码量巨大,而且速度也比快排慢)。
  4. 输出。

数据结构:

  • 按不同数据比较优先级设计结构体,优先级高者在高位,低者在低位。再用共用体将此结构体平行映射为一个长整形数。如此比较函数就不需要多个if/else了。
  • 需要注意各数据范围是否能容纳于整型存储范围。因为分数都不超过100分,总分也不超过200,可以用8位无符号整型(最多256个值)装下。而ID是8位整数,当可以用32位整型装下时,最好用整型装,因为这是机试题没必要像数据库那样用字符串。
  • 注意编译器分配变量内存的方式是"小端"还是"大端",一般C/C++编译器都是小端方式,即先声明的变量占低位。

 版本1:32位共用体版(推荐!)(C++)

分数和分类mark用共用体组成32位value值降序,当value同则ID按升序排,分两次32位比较

(使用共用体版本。使用共用体要小心,特别写入容易出错,不懂的请自行查找资料或在评论区留言)

此版本的结构体位表如下:

成员名talent才分virtue德分total总分mark优先级ID
所在比特位0~78~1516~2324~3132~64
最大整数范围\实际使用范围256 \ 101256 \ 101256 \ 201256 \ 42^32 \ 10^8

 C++代码如下:

//1062 Talent and Virtue (25分)
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
typedef unsigned int U32;
struct INFO {//INFO
	union {	//匿名共用体:由一个匿名结构体{talent, virtue, total, mark}和value共用32比特空间。
		U32 value;
		struct{	//talent:才分;virtue:德分;total:总分;mark:分类标记
			char talent, virtue, total, mark;
		};	//匿名结构体,熟练后一点也不麻烦
	};
	U32 ID;
	INFO(U32 ID, int virtue, int talent, int mark)
		:ID(ID), virtue(virtue), talent(talent), total(virtue + talent), mark(mark) {}
	bool operator<(const INFO&b) { 
		if (value == b.value)return ID < b.ID;
		return value > b.value; 
	}//用于sort(),按value降序排序,相当于按 mark,total,virtue优先顺序降序。value同则按ID升序。
};
int main() {
	int N, L, H;	//人数,及格下限,优良下限
	scanf("%d %d %d", &N, &L, &H);
	vector<INFO> A; A.reserve(N);	//预留容量为N,避免扩容浪费时间
	for (size_t i = 0; i < N; i++) {
		U32 ID; int virtue, talent;
		scanf("%u %d %d", &ID, &virtue, &talent);
		if (virtue < L || talent < L)continue;	//排除不合格者①
		if (virtue >= H) {	//德优
			A.emplace_back(ID, virtue, talent, (talent >= H) + 2);	//当(talent >= H): 为真表示圣人 mark=3;为假表示君子 mark=2
		}else {	//L ≤ virtue < H ,L ≤ talent
			A.emplace_back(ID, virtue, talent, (virtue >= talent));	//当(virtue >= talent):为真表示愚人mark=1;为假表示小人mark=0
		}
	}
	sort(A.begin(), A.end());
	printf("%d\n", A.size());
	for (auto&a : A) {	//注意ID取反(8位数字)
		printf("%08u %d %d\n", a.ID, a.virtue, a.talent);
	}
	system("pause");
	return 0;
}

笔者在网上搜不到比这更简短的此题AC代码了,不要看注释可以用正则表达式【//.*$】替换为【】(空字符串)去掉注释。

版本2:64位共用体一次性比较(C++)

  • 因为同分数时ID是升序排序的,可以用取反来实现与分数value同时运算,而不必用if判断。(不推荐取相反数,因为整型正负最大值并不对称,而且取反运算最节能)
  • 所以笔者设计了一个如下的结构体,每个成员变量及其所占的位如下表:
  • 成员名notIDtalent才分virtue德分total总分mark优先级
    所在比特位0~3132~3940~4748~5556~63
    最大整数范围\实际使用范围2^32 \ 10^8256 \ 101256 \ 101256 \ 201256 \ 4
  • 其中 notID 是ID的按位取反,以此达到整体降序排序时,ID是升序排序的效果。但要注意输出时要再次取反。

  • 只要按整个结构体所占的64位无符号整型的大小降序排序,就能达到题目要求的排序效果!

  • 如果是64位系统,理论上此方法执行速度更快!

C++代码如下:

//1062 Talent and Virtue (25分)
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
typedef unsigned int U32;
typedef unsigned long long U64;
union INFO {//INFO共用体 由一个匿名结构体{notID,talent, virtue, total, mark}和value共用64比特空间。
	struct {	//先声明的在低位
		U32 notID;	//ID的按位取反,如此可以对整体降序达到对ID升序的效果
		char talent, virtue, total, mark;	//mark在最高位
	};//talent:才分;virtue:德分;total:总分;mark:分类标记
	U64 value;	//相当于 value=(notID<<32)|(mark<<24)|(total<<16)|(virtue<<8)|talent;
	INFO(U32 ID, int virtue, int talent, int mark)
		:notID(~ID), virtue(virtue), talent(talent), total(virtue + talent), mark(mark) {}
	bool operator<(const INFO&b) { 
		return value > b.value; 
	}//用于sort(),按value降序排序,相当于按 mark,total,virtue优先顺序降序,而notID是ID取反,即相当于ID升序排序。
};	
int main() {
	int N, L, H;	//人数,及格下限,优良下限
	scanf("%d %d %d", &N, &L, &H);
	vector<INFO> A; A.reserve(N);	//预留容量为N,避免扩容浪费时间
	for (size_t i = 0; i < N; i++) {
		U32 ID; int virtue, talent;
		scanf("%u %d %d", &ID, &virtue, &talent);
		if (virtue < L || talent < L)continue;	//排除不合格者①
		if (virtue >= H) {	//德优
			A.emplace_back(ID, virtue, talent, (talent >= H) + 2);	//当(talent >= H): 为真表示圣人 mark=3;为假表示君子 mark=2
		}
		else {	//L ≤ virtue < H ,L ≤ talent
			A.emplace_back(ID, virtue, talent, (virtue >= talent));	//当(virtue >= talent):为真表示愚人mark=1;为假表示小人mark=0
		}
	}
	sort(A.begin(), A.end());
	printf("%d\n", A.size());
	for (auto&a : A) {	//注意ID取反(8位数字)
		printf("%08u %d %d\n", ~a.notID, a.virtue, a.talent);
	}
	system("pause");
	return 0;
}

此代码与版本1的32位解法基本上只有INFO结构体和最后输出不同。不信自己比较。

执行时间仅51ms!(执行时间受服务器“心情”影响会有波动,故仅供参考)

耗时:51 ms

 


附版本3:常规结构体版本(非推荐,仅作对照)

此版本不需要掌握共用体和计算机组成原理中整型的存储原理,不需要理会数据范围,ID用string字符串暴力存储。容易上手,就是要多写几个if,执行速度会慢一些。


#include<cstdio>
#include<algorithm>
#include<vector>
#include<string>
using namespace std;
struct INFO {//INFO
	int virtue, total, mark;//talent:才分;virtue:德分;total:总分;mark:分类标记
	string ID;
	INFO(const char *ID, int virtue, int talent, int mark)
		:ID(ID), virtue(virtue), total(virtue + talent), mark(mark) {}
	bool operator<(const INFO&b) { 
		if (mark != b.mark)return mark > b.mark;
		if (total != b.total)return total > b.total;
		if (virtue != b.virtue)return virtue > b.virtue;
		return ID < b.ID;	//ID是升序
	}//用于sort(),按 mark,total,virtue优先顺序降序。最后按ID升序。
	int talent()const { return total - virtue; }	//可以用总分倒推才分
};
int main() {
	int N, L, H;	//人数,及格下限,优良下限
	scanf("%d %d %d", &N, &L, &H);
	vector<INFO> A; A.reserve(N);	//预留容量为N,避免扩容浪费时间
	for (size_t i = 0; i < N; i++) {
		char ID[10]; int virtue, talent;
		scanf("%s %d %d", ID, &virtue, &talent);
		if (virtue < L || talent < L)continue;	//排除不合格者①
		if (virtue >= H) {	//德优
			A.emplace_back(ID, virtue, talent, (talent >= H) + 2);	//当(talent >= H): 为真表示圣人 mark=3;为假表示君子 mark=2
		}else {	//L ≤ virtue < H ,L ≤ talent
			A.emplace_back(ID, virtue, talent, (virtue >= talent));	//当(virtue >= talent):为真表示愚人mark=1;为假表示小人mark=0
		}
	}
	sort(A.begin(), A.end());
	printf("%d\n", A.size());
	for (auto&a : A) {	//注意ID取反(8位数字)
		printf("%s %d %d\n", a.ID.data(), a.virtue, a.talent());
	}
	system("pause");
	return 0;
}

附版本4:C语言版(不推荐,只是写来比较,C语言的qsort就是不如C++的sort好用)

如下C语言代码中,由于C语言的qsort()需要代入一个返回int型的比较函数指针,不方便直接用64位整型算出,不如将ID单独拿出来比较。

此版本的结构体位表如下:

成员名talent才分virtue德分total总分mark优先级ID
所在比特位0~78~1516~2324~3132~64
最大整数范围\实际使用范围256 \ 101256 \ 101256 \ 201256 \ 42^32 \ 10^8

C语言参考代码如下(用qsort,写cmp函数代入比较):

#include<stdio.h>
#include<stdlib.h>
typedef unsigned char U8;
typedef unsigned int U32;
#define MAX_N 100000
typedef struct {//INFO由{rvID,talent, virtue, total, mark}成员组成共用64比特空间。
				//talent:才分;virtue:德分;total:总分;mark:分类标记
	U8 talent, virtue, total, mark;	//mark在最高位
	U32 ID;		//ID在高位
}INFO;
int cmp(const void*a, const void*b) {
	if (*((U32*)b) == *((U32*)a))	//低位的分数比较值相同,则比较ID
		return ((INFO*)a)->ID - ((INFO*)b)->ID;	//升序是 a-b ;降序 b-a 。a指左参数。
	return *((U32*)b) - *((U32*)a);	//不同返回低位的分数比较值之差
}
INFO A[MAX_N]; int size_A = 0;
int main() {
	int N, L, H;	//人数,及格下限,优良下限
	scanf("%d %d %d", &N, &L, &H);
	for (int i = 0; i < N; i++) {
		U32 ID; int virtue, talent;
		//★注意不能直接 scanf("%d %d", &A[size_A].virtue, &A[size_A].talent);
		//在OJ平台Linux系统下:用scanf("%d",...)对结构体中的8位整型赋数值可能会覆盖邻近的成员导致错误!
		scanf("%u %d %d", &ID, &virtue, &talent);
		if (virtue < L || talent < L)continue;	//排除不合格者①
		A[size_A].ID = ID;
		A[size_A].virtue = virtue;
		A[size_A].talent = talent;
		A[size_A].total = virtue + talent;
		if (virtue >= H) {	//德优
			A[size_A].mark = (talent >= H) + 2;	//当(talent >= H): 为真表示圣人 mark=3;为假表示君子 mark=2
		}else {	//L ≤ virtue < H ,L ≤ talent
			A[size_A].mark = (virtue >= talent);//当(virtue >= talent):为真表示愚人mark=1;为假表示小人mark=0
		}
		++size_A;	//注意 size_A 不一定与i同步,因为有排除不合格者。
	}
	qsort(A, size_A, sizeof(INFO), cmp);
	printf("%d\n", size_A);
	for (int i = 0; i < size_A; ++i) {
		printf("%08u %d %d\n", A[i].ID, A[i].virtue, A[i].talent);
	}	//ID为 8 位整数
	system("pause");
	return 0;
}

可能大家会觉得C语言的cmp函数写得一点儿也不优雅,这是因为qsort使用的比较函数参数是void指针,你必须自己设计用什么类型,占多少字节,如何比较。所以用C语言写这个必须理解计算机的数据储存原理,C语言本来就是面向过程语言。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值