在刷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; }