Iterating over subsets of subsets costs . Aggregating over all subsets or supersets of every mask (zeta and Möbius transforms, “SOS DP”) costs . Know which one your transition really needs.

Enumeration

for (int s = m; s; s = (s - 1) & m) {}             // non-empty submasks of m, decreasing
for (int s = m; s < (1 << n); s = (s + 1) | m) {}   // supermasks of m within n bits, increasing
  • s - 1 clears the lowest set bit and sets everything below it; & m keeps only bits of . So the loop visits every submask exactly once.
  • Over all masks: . Each element is in neither, in the mask only, or in both. is fine up to - ().
  • Fixed popcount (all -subsets in increasing order): Gosper’s hack, , , . Handle as a separate base case (there’s no lowest set bit to use), and stop before would need a bit beyond the -bit universe (don’t build 1 << n when equals the type width).

Set partition DP in :

dp[0] = 0;
for (int m = 1; m < 1 << n; m++) {
    int low = m & -m;                         // fix the block containing the lowest element
    for (int s = m; s; s = (s - 1) & m)
        if ((s & low) && ok[s]) dp[m] = min(dp[m], dp[m ^ s] + cost[s]);
}

Fixing the lowest element avoids counting the same partition in every block order: for counting, it’s required; for optimisation it’s a speedup.

Zeta and Möbius transforms

for (int i = 0; i < n; i++) for (int m = 0; m < 1 << n; m++)
    if (m >> i & 1) f[m] += f[m ^ 1 << i];          // subset zeta; use -= for Möbius (inverse)
for (int i = 0; i < n; i++) for (int m = 0; m < 1 << n; m++)
    if (!(m >> i & 1)) f[m] += f[m | 1 << i];       // superset zeta; -= for inverse

The bit loop must be outside: it’s an -dimensional prefix sum with every dimension of size 2. The inverse is

which is inclusion-exclusion over a subset lattice. Any “aggregate over submasks” with an idempotent or invertible operation works the same way: max, min, gcd, OR of bitsets, counting.

Convolutions over the subset lattice

Want MethodCost
(OR)subset zeta both, multiply pointwise, Möbius
(AND)superset zeta, multiply, inverse
(XOR)Walsh-Hadamard (Bitwise Convolutions)
(disjoint union)subset convolution (ranked zeta)

Applications

ProblemTechnique
for each mask, count array elements that are submasks of itsubset zeta over the frequency array
count pairs for each , count elements that are submasks of
count pairs with full setsuperset zeta, or Möbius trick
count subsets of the array whose AND is exactly counts subsets whose AND is a supermask of ; superset Möbius gives exactly
subsets with OR exactly then subset Möbius
max with best of over submasks of (max-zeta)
for each mask, the two best values over its submasks (from distinct sources)zeta with “top two” merge
inclusion-exclusion over conditionsMöbius on counts of “violates at least “
number of -colourings / chromatic number of a graph with iff $\sum_{S}(-1)^{n-
merge two sub-results at a shared vertex (small-universe Steiner tree)subset-sum DP over the terminal set, via subset zeta/Möbius (Steiner Tree)
TSP / routing with cities grouped into groups, set cover, exact cover split DP, or subset convolution when the transition is a disjoint split (Exact Cover)
counting matchings in a small graphsplit off one vertex’s partner, sum over the rest as a subset transition
minimal covering superset of satisfying a property enumerate supermasks of in increasing numeric order and stop at the first hit — note increasing numeric order is not increasing popcount order, so for smallest cardinality sort by popcount or BFS the mask lattice instead
requirement closure (“if you take these, you must also take those”)supermask enumeration is the natural iteration order for the closure computation

The chromatic number trick (Björklund-Husfeldt-Koivisto) is worth remembering: compute for all with a zeta transform of the “is independent” indicator, then test . Do it modulo a random prime to avoid big numbers (small chance of a false negative).

Subset convolution and set power series

Disjoint union is together with . Split by popcount rank:

  1. , and the same for ; zeta-transform each rank.
  2. pointwise.
  3. Möbius-transform each and read .
// a, b: size 2^n, values mod p. Ranked arrays use O(n 2^n) memory: use 32-bit modints.
vector<mint> subsetConv(const vector<mint>& a, const vector<mint>& b, int n) {
    int N = 1 << n;
    vector<vector<mint>> A(n + 1, vector<mint>(N)), B = A, C = A;
    for (int s = 0; s < N; s++) A[popcount((unsigned)s)][s] = a[s], B[popcount((unsigned)s)][s] = b[s];
    auto zeta = [&](vector<mint>& f, bool inv) {
        for (int i = 0; i < n; i++) for (int s = 0; s < N; s++)
            if (s >> i & 1) inv ? f[s] -= f[s ^ 1 << i] : f[s] += f[s ^ 1 << i];
    };
    for (int k = 0; k <= n; k++) zeta(A[k], 0), zeta(B[k], 0);
    for (int k = 0; k <= n; k++) for (int i = 0; i <= k; i++)
        for (int s = 0; s < N; s++) C[k][s] += A[i][s] * B[k - i][s];
    vector<mint> c(N);
    for (int k = 0; k <= n; k++) zeta(C[k], 1);
    for (int s = 0; s < N; s++) c[s] = C[popcount((unsigned)s)][s];
    return c;
}

In the ranked (zeta) domain each mask holds a polynomial in the rank variable of degree , and subset convolution is polynomial multiplication there. So exp and log of set power series are pointwise polynomial exp/log of degree (with for exp), :

  • connected graphs on vertex set (): of “all graphs on ” (), or any other “connected structures = log(all structures)” relation;
  • set partitions into blocks with weights: of the block weights;
  • number of spanning forests / connected spanning subgraphs on small vertex sets.