zoukankan      html  css  js  c++  java
  • 通过位运算来解决一些算法题

    在刷pat的1073 多选题常见计分法题目时,发现如果需要判断每一个学生对应每道题的多选题是否错选,漏选,以及选对是比较麻烦的一件事,因为这涉及到两个集合的判断,判断一个集合是否是另一个集合的子集(即漏选,得一半的分),或者说两个集合是否完全相等(即题目得满分)。

    刚开始通过set容器来保存每一道题的正确答案,以及学生选择的答案,然后比较两个集合的大小,大小一致则for循环判断每一个元素是否都存在。结果发现这种思路过于复杂,且易超时。

    联想到每一个选项是否一致,可以通过异或运算判断两个集合,如果结果为0则得满分,否则就是错选或者漏选,错选漏选通过或运算来判断是否能够得到正确集合,如果可以则是漏选,如果不能则说明是错选。

    在完整的实现这道题之前,先来学习一下位运算的基础。

    1.常见的位运算

    常见的位运算有6个,见如下表格:

    Operators Meaning of operators
    & Bitwise AND,按位与
    | Bitwise OR,按位或
    ^ Bitwise XOR,按位异或
    ~ Bitwise complement,按位取反
    << Shift left,左移
    >> Shift right,右移

    1.1按位与

    运算举例,对12和25进行按位操作:

    12 = 00001100 (In Binary)
    25 = 00011001 (In Binary)
    
    Bit Operation of 12 and 25
      00001100
    & 00011001
      ________
      00001000  = 8 (In decimal)
    

    当且仅当,两个二进制位都为1时,结果才为1

    代码举例:

    #include <stdio.h>
    int main()
    {
        int a = 12, b = 25;
        printf("Output = %d", a&b);
        return 0;
    }
    

    Output:

    Output = 8
    

    1.2按位或

    运算举例,对12和25进行按位操作:

    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
    

    1.3按位异或

    运算举例,对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
    

    对于异或运算的理解

    • 找出两个数有差异的位,a^b得到的结果中,1表示在该位两数存在差别,0表示无差别,这个很好理解
    • 将一个数按照另一个数的对应位的取值改变取值,如ab(1000101000110011),可以看成a按照b的要求改变对应位的取值(1为改变,0为不改变)故得到10111001

    1.4按位取反

    运算举例,对35进行按位取反操作:

    35 = 00100011 (In Binary)
    
    Bitwise complement Operation of 35
    ~ 00100011 
      ________
      11011100  = 220 (In decimal)
    

    代码举例:

    #include <stdio.h>
    int main()
    {
        printf("Output = %d
    ",~35);
        printf("Output = %d
    ",~-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。

    1.5移位运算

    1.左移

    可以简单理解为*2,对比十进制中的左移,比如10进制的13左移1位得到130,所以

    二进制中的13左移1位得到26

    左移n位,结果就是乘以2的n次方

    1101
    <<1
    11010
    十进制为26    
    

    2.右移

    类比左移,右移就是除以2

    代码举例:

    #include <stdio.h>
    int main()
    {
        int num=212, i;
        for (i=0; i<=2; ++i)
            printf("Right shift by %d: %d
    ", i, num>>i);
         printf("
    ");
         for (i=0; i<=2; ++i) 
            printf("Left shift by %d: %d
    ", 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
    

    2.解题思路

    对于每一个选项,我都可以通过二进制来表示出来,比如

    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就是漏选的,得分50%
    • 否则就是有错选,不得分

    第三步对A和B的异或结果和{1,2,4,8,16}集合中的元素分别进行与运算,判断当前题目,学生选错的选项是哪一个

    比如:正确选项是10011,学生的答案是01100,异或结果为11111,对异或结果11111和1,2,4,8,16分别进行与运算,比如11111&00001结果不为零,则说明该选项是错误的,以此类推,循环进行与运算,得出学生选择的选项都是错误的。正确的选项都没有选,所以也记为错选选项。

    通过异或运算和或运算以及与运算来判断全选对,漏选,错选以及对应的错误选项就简单多了

    3.代码实现

    #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
    ",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
    ");
    	} 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
    ", maxcnt, i+1, 'a'+j);
    			}
    		}
    	}
    	return 0;
    }
    
  • 相关阅读:
    Tengine vs openresty
    知名黑客组织Anonymous(匿名者)的装备库
    25个让Java程序员更高效的Eclipse插件
    php提示Fatal error: Call to undefined function imagecreate()
    【转】【iOS】动态更换App图标
    unity在安卓中横屏闪退
    WWW缓存方式
    if UNITY_EDITOR这个判断常用,还有哪个常用捏?
    Lerp和SmoothDamp比较
    UNITY把3D模型显示在UI层级上的思路
  • 原文地址:https://www.cnblogs.com/ericling/p/11826619.html
Copyright © 2011-2022 走看看