Prerequisites: bitwise operations and integers and bit sequences.
The binary representation of a number can be viewed as a set, where means that an element is absent from the set and means that it is present. For example, the set can be represented as . The corresponding bitwise operations can then be viewed as operations on sets.
| Operation | Set representation | Bitwise representation |
|---|---|---|
| Intersection | ||
| Union | ||
| Complement | (the universal set is all 1s in binary) | |
| Difference | ||
| Symmetric difference |
Before introducing operations for enumerating subsets of a set, let us first look at some applications of bitwise operations.
Powers of 2 modulo
Taking a number modulo a nonnegative power of is equivalent to taking its last several bits in binary, and is equivalent to applying AND with .
C++
int modPowerOfTwo(int x, int mod) { return x & (mod - 1); }
Python
def modPowerOfTwo(x, mod):
return x & (mod - 1)
It follows that taking a nonnegative power of modulo itself gives ; in other words, if is a nonnegative power of , the result of ANDing with is .
In fact, for a positive integer , clears the lowest position of that contains , and changes all following bits to . Therefore, ANDing with is equivalent to removing the lowest position of that contains .
This can be used to determine whether a number is a nonnegative power of . If a number has exactly one in its binary representation, then is a nonnegative power of .
C++
bool isPowerOfTwo(int n) { return n > 0 && (n & (n - 1)) == 0; }
Python
def isPowerOfTwo(n):
return n > 0 and (n & (n - 1)) == 0
Subset enumeration
Enumerating all subsets of a set represented by a binary number is equivalent to enumerating all submasks of the corresponding mask.
A mask is a string of binary digits used in an AND operation with a source operand to obtain a new operand after masking out some input bits of the source.
A bit in the mask means that the corresponding bit of the source is retained, while a bit means that the corresponding bit of the source is set to . Changing some of the mask's bits to gives a submask of the mask; the mask itself is also its own submask.
Given a mask , to iterate efficiently over all submasks of , denoted , one can use the following bitwise technique:
// enumerate the non-empty subsets of m in descending order
int s = m;
while (s > 0) {
// s is a non-empty subset of m
s = (s - 1) & m;
}
Or use the more compact for statement:
// enumerate the non-empty subsets of m in descending order
for (int s = m; s; s = (s - 1) & m)
// s is a non-empty subset of m
Neither of these code snippets processes the submask equal to . To process the submask equal to , another method can be used, for example:
// enumerate all subsets of m in descending order
for (int s = m;; s = (s - 1) & m) {
// s is a subset of m
if (s == 0) break;
}
We next prove that the code above visits every submask of without repetition and in descending order.
Suppose the current bitmask is and we want to visit the next bitmask. In mask , subtracting is equivalent to removing the rightmost set bit of and changing all bits to its right to .
For to become a new submask, all extra bits not contained in mask must be removed. These extra bits can be removed with the bitwise operation (s - 1) & m.
These two operations are equivalent to cutting down to determine the largest value available in arithmetic order, namely the next submask after in descending order.
Therefore, the algorithm generates all submasks of the mask in descending order, using only two operations per iteration.
The special case is . After executing , we obtain , whose bits are all . After the operation (s - 1) & m, the new is equal to . Therefore, if the loop does not terminate at , it cannot terminate at all.
Using to denote 's binary count of bits, this method enumerates the subsets in time over .
Enumerating submasks of all masks
In problems using subset DP, it is sometimes useful to enumerate all submasks of every mask:
for (int m = 0; m < (1 << n); ++m)
// enumerate the non-empty subsets of m in descending order
for (int s = m; s; s = (s - 1) & m)
// s is a non-empty subset of m
This enumerates every subset of every subset of a set of size .
We next prove that this operation has time complexity , where is the total number of bits in the mask, or equivalently the number of elements in the set.
Consider bit , the -th element of the set. There are three possibilities:
- In mask it is , and therefore in submask it is ; the element is in neither the larger nor the smaller subset.
- In it is , but in it is ; the element is only in the larger subset and not in the smaller subset.
- In both and it is ; the element is in both subsets.
There are bits in total, so there are different combinations.
Another proof is as follows.
If mask has s, then it has submasks. For a given , there are possible masks , so the total number of masks is
The sum above is the expansion of by the binomial theorem, so there are different combinations.
References
This page is mainly translated from the blog post Перебор всех подмасок данной маски and its English translation Submask Enumeration. The Russian version is licensed under Public Domain + Leave a Link; the English version is licensed under CC-BY-SA 4.0.