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.
    1. A mask with exactly set bits has submasks.
    1. The number of masks of length with exactly set bits is given by the binomial coefficient:
    1. To find the total number of submask iterations across all possible masks, we sum this product over all from to :
    1. Applying the Binomial Theorem:
    1. Substituting and :
    1. 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 :
IterationCurrent (Decimal)Current (Binary) (Binary)Next (Decimal)
Start13--13
11312
2129
398
485
554
641
710 (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) & mask decrements 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