經過位運算來解決一些算法題

在刷pat的1073 多選題常見計分法題目時,發現若是須要判斷每個學生對應每道題的多選題是否錯選,漏選,以及選對是比較麻煩的一件事,由於這涉及到兩個集合的判斷,判斷一個集合是不是另外一個集合的子集(即漏選,得一半的分),或者說兩個集合是否徹底相等(即題目得滿分)。ios

剛開始經過set容器來保存每一道題的正確答案,以及學生選擇的答案,而後比較兩個集合的大小,大小一致則for循環判斷每個元素是否都存在。結果發現這種思路過於複雜,且易超時。數組

聯想到每個選項是否一致,能夠經過異或運算判斷兩個集合,若是結果爲0則得滿分,不然就是錯選或者漏選,錯選漏選經過或運算來判斷是否可以獲得正確集合,若是能夠則是漏選,若是不能則說明是錯選。學習

在完整的實現這道題以前,先來學習一下位運算的基礎。spa

1.常見的位運算

常見的位運算有6個,見以下表格:code

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

1.1按位與

運算舉例,對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

1.2按位或

運算舉例,對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

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;
}
相關文章
相關標籤/搜索