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, increasings - 1clears the lowest set bit and sets everything below it;& mkeeps 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 - ().
Proof: the submask loop and the bound
Enumeration. Let be a nonzero submask of (so ). Write where , so bit is the lowest set bit and is whatever sits above it. Then : bit and all bits above are unchanged except bit itself drops to , while bits (all in ) become all . ANDing with clears exactly the new low bits that doesn’t have, leaving the largest submask of strictly below in numeric value, and every bit of above position that was set in stays set (since only touches the trailing run). So the sequence is strictly decreasing, every value produced is a submask of (since each step ANDs with ), and iterating until visits all of them: induction on ‘s position shows no submask is skipped, since the construction is just “take the current submask, clear its lowest set bit, and re-fill every lower bit of ” which is exactly the predecessor of in the submask poset ordered by value. bound. sums, for every mask , the number of its submasks. Group by which of the states each of the elements is in relative to : not in (so not in either), in but not in , or in both and — these independent choices per element, over elements, give total pairs with , matching by the binomial theorem applied to .
Tested: the submask-enumeration loop was checked against a direct scan of all masks (confirming every submask of every is visited exactly once, non-submasks never visited) for up to 6.
- 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 << nwhen 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 inverseThe 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.
Proof: the zeta transform and its Möbius inverse
Zeta correctness. Process bits in order; let be the array after processing bits . Claim: over all that agree with on bits and are an arbitrary subset of ‘s bits below . Base case : , and the only ” agreeing on bits with no freedom below” is itself — matches. Inductive step: processing bit does
if m has bit i: F[m] += F[m without bit i]. If lacks bit , , consistent (no new freedom is gained since bit of every valid must also be ). If has bit , , which by the inductive hypothesis sums over ‘s that already had the right freedom below and either keep bit set (the first term) or clear it (the second, since agrees with above bit , and the inductive hypothesis for mask ranges over subsets agreeing on bits , i.e. exactly ‘s bits above , with bit cleared) — together giving exactly ” agrees with on bits , arbitrary on bits ”. After all bits, . Möbius inverse. Running the same dimension-sweep with subtraction instead of addition is literally the signed inclusion-exclusion sum: by induction the same argument shows after processing bit with subtraction, the array equals restricted appropriately, and after all bits this is exactly , which recovers because substituting and swapping summation order gives , and the inner sum is unless (it’s by the binomial theorem, which is for and for ).Tested against a direct double loop over all pairs with , confirming both the forward zeta transform and that applying the subtractive sweep to its output recovers the original array exactly, for up to 6.
Convolutions over the subset lattice
| Want | Method | Cost |
|---|---|---|
| (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
| Problem | Technique |
|---|---|
| for each mask, count array elements that are submasks of it | subset zeta over the frequency array |
| count pairs | for each , count elements that are submasks of |
| count pairs with full set | superset 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 conditions | Mö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 graph | split 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:
- , and the same for ; zeta-transform each rank.
- pointwise.
- Möbius-transform each and read .
Proof: ranked zeta/Möbius computes the disjoint-union convolution
By the zeta-transform proof above, — the sum of over rank- subsets of . The pointwise product , summing over all pairs of the given ranks, overlapping or not. Summing over and Möbius-inverting back from -indexed sums to “exactly ” (same inclusion-exclusion as the plain Möbius inverse, applied separately within rank ) gives . Restricting to with forces (if some element were in both, , contradicting ), and conversely every disjoint pair with has and . So reading off gives exactly , the disjoint-union convolution, with no overlap terms surviving.
Tested
subsetConvagainst a direct brute force enumerating every disjoint split of every , for up to 6, random integer arrays — all match.
// 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.