在刷pat的1073 多選題常見計分法題目時,發現若是須要判斷每個學生對應每道題的多選題是否錯選,漏選,以及選對是比較麻煩的一件事,由於這涉及到兩個集合的判斷,判斷一個集合是不是另外一個集合的子集(即漏選,得一半的分),或者說兩個集合是否徹底相等(即題目得滿分)。ios
剛開始經過set容器來保存每一道題的正確答案,以及學生選擇的答案,而後比較兩個集合的大小,大小一致則for循環判斷每個元素是否都存在。結果發現這種思路過於複雜,且易超時。數組
聯想到每個選項是否一致,能夠經過異或運算判斷兩個集合,若是結果爲0則得滿分,不然就是錯選或者漏選,錯選漏選經過或運算來判斷是否可以獲得正確集合,若是能夠則是漏選,若是不能則說明是錯選。學習
在完整的實現這道題以前,先來學習一下位運算的基礎。spa
常見的位運算有6個,見以下表格:code
Operators | Meaning of operators |
---|---|
& | Bitwise AND,按位與 |
| | Bitwise OR,按位或 |
^ | Bitwise XOR,按位異或 |
~ | Bitwise complement,按位取反 |
<< | Shift left,左移 |
>> | Shift right,右移 |
運算舉例,對12和25進行按位與操做:ci
12 = 00001100 (In Binary) 25 = 00011001 (In Binary) Bit Operation of 12 and 25 00001100 & 00011001 ________ 00001000 = 8 (In decimal)
當且僅當,兩個二進制位都爲1時,結果才爲1get
代碼舉例:hash
#include <stdio.h> int main() { int a = 12, b = 25; printf("Output = %d", a&b); return 0; }
Output:it
Output = 8
運算舉例,對12和25進行按位或操做:io
12 = 00001100 (In Binary) 25 = 00011001 (In Binary) Bitwise OR Operation of 12 and 25 00001100 | 00011001 ________ 00011101 = 29 (In decimal)
當且僅當,兩個二進制位都爲0時,結果才爲0,其它狀況都爲1
代碼舉例:
#include <stdio.h> int main() { int a = 12, b = 25; printf("Output = %d", a|b); return 0; }
Output:
Output = 29
運算舉例,對12和25進行按位異或操做:
12 = 00001100 (In Binary) 25 = 00011001 (In Binary) Bitwise XOR Operation of 12 and 25 00001100 ^ 00011001 ________ 00010101 = 21 (In decimal)
當且僅當,兩個二進制位相異時,結果才爲1,其它狀況都爲0
代碼舉例:
#include <stdio.h> int main() { int a = 12, b = 25; printf("Output = %d", a^b); return 0; }
Output:
Output = 21
對於異或運算的理解
運算舉例,對35進行按位取反操做:
35 = 00100011 (In Binary) Bitwise complement Operation of 35 ~ 00100011 ________ 11011100 = 220 (In decimal)
代碼舉例:
#include <stdio.h> int main() { printf("Output = %d\n",~35); printf("Output = %d\n",~-12); return 0; }
Output:
Output = -36 Output = 11
爲何這裏35按位取反的結果不是220,而是-36。
對於任何整數n,n的按位取反將爲-(n + 1)。要了解這一點,須要瞭解二進制的補碼錶示
十進制 二進制 二進制補碼 0 00000000 -(11111111+1) = -00000000 = -0(decimal) 1 00000001 -(11111110+1) = -11111111 = -256(decimal) 12 00001100 -(11110011+1) = -11110100 = -244(decimal) 220 11011100 -(00100011+1) = -00100100 = -36(decimal)
35的按位補碼爲220(十進制)。 220的2的補碼是-36。所以,輸出是-36而不是220。
能夠簡單理解爲*2,對比十進制中的左移,好比10進制的13左移1位獲得130,因此
二進制中的13左移1位獲得26
左移n位,結果就是乘以2的n次方
1101 <<1 11010 十進制爲26
類比左移,右移就是除以2
代碼舉例:
#include <stdio.h> int main() { int num=212, i; for (i=0; i<=2; ++i) printf("Right shift by %d: %d\n", i, num>>i); printf("\n"); for (i=0; i<=2; ++i) printf("Left shift by %d: %d\n", i, num<<i); return 0; }
Output
Right Shift by 0: 212 Right Shift by 1: 106 Right Shift by 2: 53 Left Shift by 0: 212 Left Shift by 1: 424 Left Shift by 2: 848
對於每個選項,我均可以經過二進制來表示出來,好比
a--00001 b--00010 c--00100 d--01000 e--10000 //由於選項個數在[2,5]區間,因此最大選項就是e
這樣的話,經過兩個集合(集合A={全部的正確選項的二進制表示的或運算結果},集合B={全部學生的選項的二進制表示的或運算結果})
好比
A=10001,即正確的選項爲ae
B=10000,即學生的選項爲e
第一步對A和B進行異或運算,
若是結果爲0,說明滿分
若是結果不爲0,說明存在選項不一致,可能漏選,可能錯選
第二步對A和B進行或運算,
第三步對A和B的異或結果和{1,2,4,8,16}集合中的元素分別進行與運算,判斷當前題目,學生選錯的選項是哪個
好比:正確選項是10011,學生的答案是01100,異或結果爲11111,對異或結果11111和1,2,4,8,16分別進行與運算,好比11111&00001結果不爲零,則說明該選項是錯誤的,以此類推,循環進行與運算,得出學生選擇的選項都是錯誤的。正確的選項都沒有選,因此也記爲錯選選項。
經過異或運算和或運算以及與運算來判斷全選對,漏選,錯選以及對應的錯誤選項就簡單多了
#include<iostream> #include<vector> using namespace std; int main() { //1.保存全部的題目信息 int n,m; scanf("%d%d",&n,&m); //a=00001=1 //b=00010=2 //c=00100=4 //d=01000=8 //e=10000=16 int hash[5] = {1,2,4,8,16} , trueopt[m]= {0}; int fullscore[m]; //1.記錄每道題的總分到fullscore數組,每道題的正確選項到trueopt for(int i=0; i<m; i++) { int tmpscore,tmpalloptsize,tmprightoptsize; scanf("%d%d%d",&tmpscore,&tmpalloptsize,&tmprightoptsize); fullscore[i] = tmpscore; for(int j=0; j<tmprightoptsize; j++) { char tmpopt; scanf(" %c",&tmpopt); trueopt[i] +=hash[tmpopt-'a']; } } //記錄每道題每一個選項的出錯次數 vector<vector<int>> cnt(m,vector<int>(5)); //2.計算每一個學生的分數,並保存錯誤選項出錯次數到cnt中 for(int i=0; i<n; i++) { double stuscore = 0; for(int j=0; j<m; j++) { getchar(); int k; scanf("(%d",&k); int selectedopt = 0; for(int o=0; o<k; o++) { char tmpc; scanf(" %c",&tmpc); selectedopt+=hash[tmpc-'a']; } scanf(")"); //計算異或結果 int result = selectedopt^trueopt[j]; if(result) { //不爲零,有漏選或錯選,進行或運算 int huo = selectedopt|trueopt[j]; if(huo == trueopt[j]) { //漏選 stuscore += fullscore[j]*1.0/2; } if(result) { //錯選,不得分,記錄錯誤選項 for (int k = 0; k < 5; k++) if (result & hash[k]) cnt[j][k]++; } } else { //滿分 stuscore += fullscore[j]; } } printf("%.1f\n",stuscore); } //循環遍歷cnt錯誤選項最多的 int maxcnt =0; for(int i=0; i<m; i++) { for(int j=0; j<cnt[i].size(); j++) { maxcnt = cnt[i][j]>maxcnt?cnt[i][j]:maxcnt; } } if (maxcnt == 0) { printf("Too simple\n"); } else { for (int i = 0; i < m; i++) { for (int j = 0; j < cnt[i].size(); j++) { if (maxcnt == cnt[i][j]) printf("%d %d-%c\n", maxcnt, i+1, 'a'+j); } } } return 0; }