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~7 8~15 16~23 24~31 32~64
最大整數範圍\實際使用範圍 256 \ 101 256 \ 101 256 \ 201 256 \ 4 2^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判斷。(不推薦取相反數,因爲整型正負最大值並不對稱,而且取反運算最節能)
  • 所以筆者設計了一個如下的結構體,每個成員變量及其所佔的位如下表:
  • 成員名 notID talent才分 virtue德分 total總分 mark優先級
    所在比特位 0~31 32~39 40~47 48~55 56~63
    最大整數範圍\實際使用範圍 2^32 \ 10^8 256 \ 101 256 \ 101 256 \ 201 256 \ 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~7 8~15 16~23 24~31 32~64
最大整數範圍\實際使用範圍 256 \ 101 256 \ 101 256 \ 201 256 \ 4 2^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語言本來就是面向過程語言。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章