What is Submask Bitwise Iteration?
Submask Bitwise Iteration is an advanced bit manipulation hack that allows us to iterate through all submasks of a given bitmask in time per submask. When applied to all masks of size , this reduces the complexity of subset-iteration algorithms from a naive to an optimized .
Explanation
The Problem
- Given a bitmask , we want to find all submasks such that is a subset of .
- Mathematically, is a submask of if and only if:
- This means can only have set bits (s) at positions where also has set bits.
The Naive Way
- To find all submasks of , we could loop an integer from down to , checking the subset condition for each one:
for (int s = M; s >= 0; s--) {
if ((s & M) == s) {
// s is a valid submask
}
}- If we do this for all masks from to , the total iterations would be , which is highly inefficient for .
The Hacky Way
- We can skip all invalid masks directly in using the following bitwise decrement and mask formula:
for (int s = M; s > 0; s = (s - 1) & M) {
// s is a valid submask (excluding 0)
}- Subtracting from flips the rightmost set bit to and turns all trailing zeros to s.
- Performing a bitwise AND with (
& M) then instantly discards any bits that were not set in the original mask , snapping to the next valid submask.
Mathematical Proof of Complexity
- Why does this optimization reduce the total complexity of iterating submasks of all masks from to ?
- Let be the number of bits.
-
- A mask with exactly set bits has submasks.
-
- The number of masks of length with exactly set bits is given by the binomial coefficient:
-
- To find the total number of submask iterations across all possible masks, we sum this product over all from to :
-
- Applying the Binomial Theorem:
-
- Substituting and :
-
- Therefore:
- This is a massive improvement. For :
- operations (takes several minutes, TLE).
- operations (takes a few seconds).
Step-by-Step Trace ()
- Let ( in binary). Let’s trace the values generated by :
| Iteration | Current (Decimal) | Current (Binary) | (Binary) | Next (Decimal) | |
|---|---|---|---|---|---|
| Start | 13 | - | - | 13 | |
| 1 | 13 | 12 | |||
| 2 | 12 | 9 | |||
| 3 | 9 | 8 | |||
| 4 | 8 | 5 | |||
| 5 | 5 | 4 | |||
| 6 | 4 | 1 | |||
| 7 | 1 | 0 (Loop ends) |
- Submasks found: 13 (), 12 (), 9 (), 8 (), 5 (), 4 (), 1 (), and implicitly 0 ().
Application: Subset Dynamic Programming
- Consider the problem of partitioning items into groups, where each subset has a known cost , and we want to find the minimum cost to cover all items.
- Let be the minimum cost to cover the subset represented by .
- The recurrence relation is:
- Using the submask iteration trick, we can solve this in time.
Implementation
-
Below are the implementations showing how to iterate submasks of a given mask, and how to execute the subset DP transition. Languages: Python · Cpp · Java Script · Java
# 1. Iterate submasks of a single mask
def get_submasks(mask):
submasks = []
s = mask
while s > 0:
submasks.append(s)
s = (s - 1) & mask
submasks.append(0) # Add the empty set
return submasks
# 2. Iterate all submasks for all masks up to N (O(3^N))
def iterate_all(n):
total_iterations = 0
for mask in range(1 << n):
s = mask
while s > 0:
total_iterations += 1
s = (s - 1) & mask
total_iterations += 1 # For s = 0
return total_iterations
# Example
print("Submasks of 13:", get_submasks(13))
print("Total iterations for N=5 (3^5 = 243):", iterate_all(5))#include <iostream>
#include <vector>
// 1. Iterate submasks of a single mask
std::vector<int> get_submasks(int mask) {
std::vector<int> submasks;
for (int s = mask; s > 0; s = (s - 1) & mask) {
submasks.push_back(s);
}
submasks.push_back(0); // Empty set
return submasks;
}
// 2. Sample O(3^N) DP skeleton
void subset_dp(int n, const std::vector<int>& cost) {
std::vector<int> dp(1 << n, 1e9);
dp[0] = 0;
for (int mask = 1; mask < (1 << n); ++mask) {
for (int s = mask; s > 0; s = (s - 1) & mask) {
dp[mask] = std::min(dp[mask], dp[s] + cost[mask ^ s]);
}
}
}
int main() {
auto subs = get_submasks(13);
std::cout << "Submasks of 13:\n";
for (int x : subs) std::cout << x << " ";
std::cout << "\n";
return 0;
}// 1. Iterate submasks of a single mask
function getSubmasks(mask) {
const submasks = [];
let s = mask;
while (s > 0) {
submasks.push(s);
s = (s - 1) & mask;
}
submasks.push(0);
return submasks;
}
// Example
console.log("Submasks of 13:", getSubmasks(13));import java.util.*;
public class SubmaskIteration {
// 1. Get all submasks of a single mask
public static List<Integer> getSubmasks(int mask) {
List<Integer> submasks = new ArrayList<>();
for (int s = mask; s > 0; s = (s - 1) & mask) {
submasks.add(s);
}
submasks.add(0);
return submasks;
}
public static void main(String[] args) {
System.out.println("Submasks of 13: " + getSubmasks(13));
}
}
Key Takeaways
- Decrement-AND Operation — The bitwise formula
s = (s - 1) & maskdecrements the current submask and crops it to the mask, jumping to the next valid submask in . - O(3^N) Bound — Proven by the binomial theorem, this optimization enables subset-based DP solutions to scale to (compared to for ).
More Learn
Resources
Related Pages
- Bitwise Hacks – General bitwise manipulation shortcuts
- Gospers Hack – Generating combinations of fixed bits
- Linear Basis Algorithm – XOR vector basis algorithms
- DSA Algo & System Design – Master DSA list