Problem
Suppose, you’ve N elements and you need to find the subsets of these N elements where zero or more of the N elements are included or excluded in a subset.
All of these subsets are called the power set. The powerset of a set S is denoted as P(S) or 2S. If S is a set with the N number of elements, then the number of all the subsets (powerset) of S is 2n. This is also the reason of the notation 2S denoting the power set P(S).
Bitmasks
We know that integers are stored as a sequence of bits (0 & 1). Now suppose we need to represent a subset of N elements, we could use an integer 0 <= x < 2N
such that, if ith bit of x is 1
it means ith
element is included in the subset, and if ith bit of x is 0
, the ith
element is not included in the subset.
Let N = 3, then the powerset of N: 23 = 8 and 0 <= x < 8:
n2 n1 n0 0 0 0 (0) 0 0 1 (1) 0 1 0 (2) 0 1 1 (3) 1 0 0 (4) 1 0 1 (5) 1 1 0 (6) 1 1 1 (7) Here, x = 110(6) means a subset with element n0 excluded and elements n1 & n2 included in the subset.
Bitmask Operations
Multiplication & Division
Multiplication Let N = 28, N (binary representation) = (11100)2 (base 2) N << 1 (shifting bits left by 1) = 56 (111000)2 Shifting bits left by n, means multiplying by 2^n Division Similarly, shifting bits right by n, means dividing by 2^n, and we lose n least significant bits (ie. right-most bits) N = 27, (11011)2 N >> 1 (shifting bits right by 1) = 13 (1101)2 Here we divided by 2 and lost 1 least significant bit. If we shift by 2: N = N >> 2 N = 6 (110)2 (divided by 2^2 (4) and lost last 2 significant bits)
Including & Excluding the ith element
NOTE: Here elements are counted from the 0th bit
Including ith bit Let N = 28 (11100) and include the ith bit, i = 1 (2nd element): To include an element we will use OR (|) operator i = 1 << 1 = 00010 (2) N = N | i = 11110 (30) Excluding ith bit Let N = 28 (11100) and exclude the ith bit, i = 2 (3rd element) To exclude an element we will use AND (&) and NOT (~) operator i = ~(1 << 2) = ~(00100) = (11011) N = N & i = 11000 (24)
Check if the ith element is present
Let N = 28 (11100) and i = 2 (3rd element) To check if ith element is present, we will use AND (&) operator i = 1 << 2 x = N & i if x = 0, ith element is not present, if x = 1 ith element is present
Toggle the status of the ith element
Let N = 28 (11100) and i = 2 (3rd element) To toggle the status of if ith element, we will use XOR (^) operator i = 1 << 2 N = N^i = (11100)^(00100) = 11000 (24)
Practice Problem
Print all the subsets of the given set of N elements
such that k elements
(k1, k2, k3)
are excluded in the subset. You’re given N
and an array ki[]
of k
elements.
Constraints
- N <= 20
- k <= N
- ki < N
Sample Input
N = 3 k = 1 ki = {0}
Sample Output
000 010 100 110
Explanation
For N = 3, the subsets are as follows: n2 n1 n0 0 0 0 (0) 0 0 1 (1) 0 1 0 (2) 0 1 1 (3) 1 0 0 (4) 1 0 1 (5) 1 1 0 (6) 1 1 1 (7) Now, the subsets with n0 not included or n0 = 0 are the following: n2 n1 n0 0 0 0 (0) 0 1 0 (2) 1 0 0 (4) 1 1 0 (6)
Solution
Approach:
Since n is small, we could use a brute force approach of generating all the subsets (2n) and removing the ones that contains any of the excluded ki (k1, k2, k3..) element.
We’ll be using C++ STL Bitset for representing subsets. Bitset class is like Arrays but stores only boolean values (true/false, 0/1). Also, Bitset is optimized for space allocation: generally, each element occupies only one bit. You could read more about it here.
Code Implementation
//
// main.cpp
// Bitmask
//
// Created by Himanshu on 18/07/22.
//
#include<iostream>
#include<cmath>
#include <bitset>
#include<vector>
#define N 3
using namespace std;
void findSubsets (int k, int ki[]) {
long subsetCount = (long int)pow(2,(double)N);
for (long i=0; i<subsetCount; i++) {
int pos = 0; //variable for current bit position (0th - N-1th)
long x = i; //integer to be used for bitmasking (0 <= x < 2^N)
bool flag = false;
bitset<N> subset;
while (x > 0) {
//Checking if the last bit (rightmost bit (1 << 0)) is set or not
if (x&1) {
//Checking if the current bit is the excluded (ki) bit or not.
//Discarding this subset if it includes an excluded bit.
for (int j=0; j<k; j++) {
if (pos == ki[j]) {
flag = true;
}
}
if (flag) {
//Discarding this subset
break;
} else {
//Adding current bit to the subset
subset.set(pos, 1);
}
} else {
//Setting the current bit as 0 if it's unset in x
subset.set(pos, 0);
}
//shifting 1 bit to right, to check the next bit
//and incresing position (pos) of the current bit by 1
x = x >> 1;
pos++;
}
if (!flag) {
cout<<subset<<endl;
}
}
}
int main() {
int k = 1;
int *ki = new int[k]();
ki[0] = 0;
cout<<"Subsets with 0th bit unset:"<<endl;
findSubsets(k, ki);
return 0;
}
Output
Subsets with 0th bit unset: 000 010 100 110
Here’s a working example: Finding Subsets
Similar Practice Problem
Codechef – Mike and Stamps
One thought on “Bitmasking”