通过位运算来解决一些算法题

在刷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表示无差别,这个很好理解
  • 将一个数按照另一个数的对应位的取值改变取值,如a^b(10001010^00110011),可以看成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\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。

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\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

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\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;
}

原文地址:https://www.cnblogs.com/ericling/p/11826619.html

时间: 2024-08-29 21:53:01

通过位运算来解决一些算法题的相关文章

位运算的操作与算法

在上一次的博客中,我们实现了使用位操作去实现四则运算.实现整数的加减乘除.这次我们将讨论位运算在算法中的一些妙用. 位运算可以进行的骚操作 在这里我将使用题目进行示例 题1:找出唯一成对的数 1-1000这1000个数放在含有1001个元素的数组中,只有唯一的一个元素值重复,其它均只出现一次.每个数组元素只能访问一次,设计一个算法,将它找出来;不用辅助存储空间,能否设计一 个算法实现? 这个题目有两个要注意的点 数的范围是1-1000,这个是确定的 不能使用辅助储存空间 只有一个数字g重复 那么

一行代码就能解决的算法题

下文是我在 LeetCode 刷题过程中总结的三道有趣的「脑筋急转弯」题目,可以使用算法编程解决,但只要稍加思考,就能找到规律,直接想出答案. 一.Nim 游戏 游戏规则是这样的:你和你的朋友面前有一堆石子,你们轮流拿,一次至少拿一颗,最多拿三颗,谁拿走最后一颗石子谁获胜. 假设你们都很聪明,由你第一个开始拿,请你写一个算法,输入一个正整数 n,返回你是否能赢(true 或 false). 比如现在有 4 颗石子,算法应该返回 false.因为无论你拿 1 颗 2 颗还是 3 颗,对方都能一次性

基于DP+位运算的RMQ算法

来源:http://blog.csdn.net/y990041769/article/details/38405063 RMQ算法,是一个快速求区间最值的离线算法,预处理时间复杂度O(n*log(n)),查询O(1),所以是一个很快速的算法,当然这个问题用线段树同样能够解决. 问题:给出n个数ai,让你快速查询某个区间的的最值. 算法分类:DP+位运算 算法分析:这个算法就是基于DP和位运算符,我们用dp[i ][j]表示从第 i 位开始,到第 i + 2^j -1 位的最大值或者最小值. 那么

牛逼!一行代码居然能解决这么多曾经困扰我半天的算法题

春节假期这么长,干啥最好?当然是折腾一些算法题了,下面给大家讲几道一行代码就能解决的算法题,当然,我相信这些算法题你都做过,不过就算做过,也是可以看一看滴,毕竟,你当初大概率不是一行代码解决的. 学会了一行代码解决,以后遇到面试官问起的话,就可以装逼了. 一.2 的幂次方 问题描述:判断一个整数 n 是否为 2 的幂次方 对于这道题,常规操作是不断这把这个数除以 2,然后判断是否有余数,直到 n 被整除成 1 . 我们可以把 n 拆成二进制看待处理的,如果 n 是 2 的幂次方的话,那么 n 的

面试官,求求你不要问我这么简单但又刁难的算法题了

有时候面试官往往会问我们一些简单,但又刁难的问题,主要是看看你对问题的处理思路.如果你没接触过这些问题,可能一时之间还真不知道怎么处理才比较好,这种题更重要的是一种思维的散发吧,今天就来分享几道题面试中遇到的算法题(当然,不是我自己遇到过,是别人遇到过,我挑选出来的) 案例1 题目描述:求1+2+3+...+n,要求不能使用乘除法.for.while.if.else.switch.case等关键字及条件判断语句(A?B:C). 我去,求和居然不让用乘除法,也不准我们用循环,如果单独这两个限制的话

ch0103 位运算,简单状压dp

题意:n个顶点带权无向图,求最短hamilton路径长度(从起点0走到终点n-1,且经过每个顶点恰好一次的路径) 在看位运算的时候做到这题,觉得状态压缩的思路挺奇特的.本来n<20,O(n!*n)的算法肯定炸了,但是可以二进制表示状态 如果将i表示为二进制,i的第j位走过就为1,没走过就为0(注意二进制位从0~n-1),此处1<=i<2^n-1 用dp[i][j]表示状态i,到达第j个城市的最小值,那么i的二进制第j位为1(到达第j个城市),且i的二进制第j位取反之后,二进制第k位为1(

常见算法题合辑(一)

这一章的内容,有些之前已经在微信公众号中将详细的思路及步骤汇总过,有些之后可能会再找时间对其进行分析,这里只将最终实现罗列出来,难易程度不分先后,算法复杂度不保证是最优,留给大家空间自行思考,当然,本章用的是C#语言进行编码,大家可以使用自己熟悉的语言将这些算法实现一遍哦~ 如果你有什么有趣的算法题或者没能解决的算法题,也可以留言给小编,让我们一起玩转算法~ 1. 冒泡排序 这个算是所有算法中最为简单的了,实现方法如下: 2. 插入排序 从排序算法来看,这个算法也是属于比较简单的了,实现方法如下

位运算之——按位与(&amp;)操作——(快速取模算法)

由于位运算直接对内存数据进行操作,不需要转成十进制,因此处理速度非常快. 按位与(Bitwise AND),运算符号为& a&b 的操作的结果:a.b中对应位同时为1,则对应结果位也为1. 例如: 10010001101000101011001111000 & 111111100000000 --------------------------------------------- 10101100000000 对10101100000000进行右移8位得到的是101011,这就得

笔试算法题(20):寻找丑数 &amp; 打印1到N位的所有的数

出题:将只包含2,3,5的因子的数称为丑数(Ugly Number),要求找到前面1500个丑数: 分析: 解法1:依次判断从1开始的每一个整数,2,3,5是因子则整数必须可以被他们其中的一个整除,如果不包含任何其他因子则最终的结果为1: 解法2:小丑数必然是某个大丑数的因子,也就是乘以2,3,或者5之后的值,所以可以利用已经找到的丑数来寻找下一个丑数,使用数组有序保存已经找到的丑 数,并且当前最大丑数值为M:用大于M/2的丑数乘以2得到M1,用大于M/3的丑数乘以3得到M2,用大于M/5的丑数