All Euler problems
Project Euler

Combined Volume of Cuboids

An axis-aligned cuboid with parameters {(x_0, y_0, z_0), (delta x, delta y, delta z)} consists of all points (X, Y, Z) with x_0 <= X <= x_0 + delta x, y_0 <= Y <= y_0 + delta y, z_0 <= Z <= z_0 + d...

Source sync Apr 19, 2026
Problem #0212
Level Level 12
Solved By 1,635
Languages C++, Python
Answer 328968937309
Length 426 words
modular_arithmeticgeometrylinear_algebra

Problem Statement

This archive keeps the full statement, math, and original media on the page.

An axis-aligned cuboid, specified by parameters $\{(x_0, y_0, z_0), (dx, dy, dz)\}$, consists of all points $(X,Y,Z)$ such that $x_0 \le X \le x_0 + dx$, $y_0 \le Y \le y_0 + dy$ and $z_0 \le Z \le z_0 + dz$. The volume of the cuboid is the product, $dx \times dy \times dz$. The combined volume of a collection of cuboids is the volume of their union and will be less than the sum of the individual volumes if any cuboids overlap.

Let $C_1, \dots, C_{50000}$ be a collection of $50000$ axis-aligned cuboids such that $C_n$ has parameters

\begin{align*} x_0 &= S_{6n - 5} \bmod 10000\\ y_0 &= S_{6n - 4} \bmod 10000\\ z_0 &= S_{6n - 3} \bmod 10000\\ dx &= 1 + (S_{6n - 2} \bmod 399)\\ dy &= 1 + (S_{6n - 1} \bmod 399)\\ dz &= 1 + (S_{6n} \bmod 399) \end{align*}

where $S_1,\dots,S_{300000}$ come from the "Lagged Fibonacci Generator":

  • For $1 \le k \le 55$, $S_k = [100003 - 200003k + 300007k^3] \pmod{1000000}$.

  • For $56 \le k$, $S_k = [S_{k -24} + S_{k - 55}] \pmod{1000000}$.

Thus, $C_1$ has parameters $\{(7,53,183),(94,369,56)\}$,

$C_2$ has parameters $\{(2383,3563,5079),(42,212,344)\}$, and so on.

The combined volume of the first $100$ cuboids, $C_1, \dots, C_{100}$, is $723581599$.

What is the combined volume of all $50000$ cuboids, $C_1, \dots, C_{50000}$?

Problem 212: Combined Volume of Cuboids

Mathematical Development

Definition 1. Let C={C1,,Cn}\mathcal{C} = \{C_1, \ldots, C_n\} be a collection of axis-aligned cuboids in R3\mathbb{R}^3. The combined volume is vol ⁣(i=1nCi)\operatorname{vol}\!\bigl(\bigcup_{i=1}^{n} C_i\bigr), where vol\operatorname{vol} denotes 3-dimensional Lebesgue measure.

Theorem 1 (Cavalieri’s principle for cuboid unions). Let z1<z2<<zMz_1 < z_2 < \cdots < z_M be the distinct zz-coordinates among all faces {z0(i),z0(i)+δz(i)}i=1n\{z_0^{(i)}, z_0^{(i)} + \delta z^{(i)}\}_{i=1}^{n}. Then

vol ⁣(i=1nCi)=j=1M1(zj+1zj)Aj\operatorname{vol}\!\left(\bigcup_{i=1}^{n} C_i\right) = \sum_{j=1}^{M-1} (z_{j+1} - z_j) \cdot A_j

where AjA_j is the 2-dimensional Lebesgue measure of the union of the xyxy-projections of all cuboids active in the slab [zj,zj+1)[z_j, z_{j+1}), i.e., those CiC_i with z0(i)zjz_0^{(i)} \leq z_j and zj+1z0(i)+δz(i)z_{j+1} \leq z_0^{(i)} + \delta z^{(i)}.

Proof. By Cavalieri’s principle, vol(U)=Area(U{z=t})dt\operatorname{vol}(U) = \int_{-\infty}^{\infty} \operatorname{Area}(U \cap \{z = t\})\,dt. For t(zj,zj+1)t \in (z_j, z_{j+1}), the set of cuboids containing the plane z=tz = t is precisely the set of cuboids active in the slab [zj,zj+1)[z_j, z_{j+1}), since no cuboid face lies strictly between zjz_j and zj+1z_{j+1}. Hence the cross-sectional area Area(U{z=t})\operatorname{Area}(U \cap \{z = t\}) is constant on each open interval (zj,zj+1)(z_j, z_{j+1}), equal to AjA_j. Integrating over each slab gives (zj+1zj)Aj(z_{j+1} - z_j) \cdot A_j, and summing over all slabs yields the total volume. \square

Theorem 2 (2D area via xx-sweep). For a fixed zz-slab, let R\mathcal{R} be the set of active xyxy-rectangles. Let x1<x2<<xKx_1 < x_2 < \cdots < x_K be the distinct xx-coordinates among all left and right edges of rectangles in R\mathcal{R}. Then

Aj==1K1(x+1x)LA_j = \sum_{\ell=1}^{K-1} (x_{\ell+1} - x_\ell) \cdot L_\ell

where LL_\ell is the total length of the union of yy-intervals from rectangles in R\mathcal{R} that cover the strip [x,x+1)[x_\ell, x_{\ell+1}).

Proof. Apply Cavalieri’s principle in dimension 2: Aj=Length(section at x)dxA_j = \int_{-\infty}^{\infty} \operatorname{Length}(\text{section at } x)\,dx. Between consecutive xx-boundaries, the set of active rectangles is constant, so the yy-union length is constant on each strip. \square

Lemma 1 (Interval union via greedy merge). Given intervals [a1,b1],,[am,bm][a_1, b_1], \ldots, [a_m, b_m] sorted by left endpoint (a1a2ama_1 \leq a_2 \leq \cdots \leq a_m), the union length is computed by the following algorithm in Θ(m)\Theta(m) time:

  1. Initialize (c,d)(a1,b1)(c, d) \leftarrow (a_1, b_1) and λ0\lambda \leftarrow 0.
  2. For k=2,,mk = 2, \ldots, m: if akda_k \geq d, set λλ+(dc)\lambda \leftarrow \lambda + (d - c) and (c,d)(ak,bk)(c, d) \leftarrow (a_k, b_k); otherwise set dmax(d,bk)d \leftarrow \max(d, b_k).
  3. Set λλ+(dc)\lambda \leftarrow \lambda + (d - c).

Then λ=Length ⁣(k=1m[ak,bk])\lambda = \operatorname{Length}\!\bigl(\bigcup_{k=1}^{m} [a_k, b_k]\bigr).

Proof. The algorithm maintains the invariant that [c,d][c, d] is the current maximal merged interval. Since the intervals are sorted by left endpoint, any new interval [ak,bk][a_k, b_k] either extends the current interval (if ak<da_k < d) or starts a disjoint component (if akda_k \geq d). In the latter case, [c,d][c, d] is closed and its length added to λ\lambda. The final addition accounts for the last open interval. Correctness follows by induction on mm. \square

Editorial

We use dynamic programming over the state space implied by the derivation, apply each admissible transition, and read the answer from the final table entry.

Pseudocode

    Z = sorted distinct set of {z0, z0 + dz} for all cuboids
    V = 0

    For j from 1 to |Z| - 1:
        dz = Z[j+1] - Z[j]
        active = {c in cuboids : c.z0 <= Z[j] and Z[j+1] <= c.z0 + c.dz}
        If active is empty then continue

        X = sorted distinct set of {c.x0, c.x0 + c.dx} for c in active
        area = 0

        For l from 1 to |X| - 1:
            dx = X[l+1] - X[l]
            intervals = [(c.y0, c.y0 + c.dy) : c in active, c.x0 <= X[l], X[l+1] <= c.x0 + c.dx]
            If intervals is empty then continue
            sort intervals by left endpoint
            Ly = GreedyMergeLength(intervals)
            area += dx * Ly

        V += dz * area

    Return V

Complexity Analysis

Let n=50,000n = 50{,}000 denote the number of cuboids.

Time. There are O(n)O(n) distinct zz-values, yielding O(n)O(n) slabs. For each slab, identifying the active set costs O(n)O(n). The xx-sweep processes O(n)O(n) strips, and for each strip the yy-interval merge costs O(klogk)O(k \log k) where kk is the number of active rectangles in that strip. Worst-case total: O(n2klogk)O(n^2 \cdot k \log k). In practice, the pseudorandom distribution over a 10,000310{,}000^3 grid produces sparse overlaps, yielding average-case O(n2)O(n^2) behavior.

Space. O(n)O(n) for cuboid storage and working arrays.

Answer

328968937309\boxed{328968937309}

Code

Each problem page includes the exact C++ and Python source files from the local archive.

C++ project_euler/problem_212/solution.cpp
#include <bits/stdc++.h>
using namespace std;

int main() {
    const int NCUBOIDS = 50000;
    const int SLEN = 6 * NCUBOIDS;

    vector<long long> S(SLEN + 1);
    for (int k = 1; k <= 55; k++) {
        long long k3 = (long long)k * k * k;
        S[k] = ((100003LL - 200003LL * k + 300007LL * k3) % 1000000LL + 1000000LL) % 1000000LL;
    }
    for (int k = 56; k <= SLEN; k++)
        S[k] = (S[k - 24] + S[k - 55]) % 1000000LL;

    struct Cuboid { int x1, y1, z1, x2, y2, z2; };
    vector<Cuboid> cuboids(NCUBOIDS);

    for (int n = 1; n <= NCUBOIDS; n++) {
        int i = n - 1;
        cuboids[i] = {
            (int)(S[6*n-5] % 10000), (int)(S[6*n-4] % 10000), (int)(S[6*n-3] % 10000),
            (int)(S[6*n-5] % 10000 + 1 + S[6*n-2] % 399),
            (int)(S[6*n-4] % 10000 + 1 + S[6*n-1] % 399),
            (int)(S[6*n-3] % 10000 + 1 + S[6*n] % 399)
        };
    }

    vector<int> zcoords;
    for (int i = 0; i < NCUBOIDS; i++) {
        zcoords.push_back(cuboids[i].z1);
        zcoords.push_back(cuboids[i].z2);
    }
    sort(zcoords.begin(), zcoords.end());
    zcoords.erase(unique(zcoords.begin(), zcoords.end()), zcoords.end());

    long long totalVolume = 0;

    for (int zi = 0; zi + 1 < (int)zcoords.size(); zi++) {
        int z0 = zcoords[zi], z1 = zcoords[zi + 1];
        int dz = z1 - z0;

        vector<int> active;
        for (int i = 0; i < NCUBOIDS; i++)
            if (cuboids[i].z1 <= z0 && z1 <= cuboids[i].z2)
                active.push_back(i);
        if (active.empty()) continue;

        vector<int> localx;
        for (int i : active) {
            localx.push_back(cuboids[i].x1);
            localx.push_back(cuboids[i].x2);
        }
        sort(localx.begin(), localx.end());
        localx.erase(unique(localx.begin(), localx.end()), localx.end());

        for (int xi = 0; xi + 1 < (int)localx.size(); xi++) {
            int x0 = localx[xi], x1 = localx[xi + 1];
            int dx = x1 - x0;

            vector<pair<int, int>> yintervals;
            for (int i : active)
                if (cuboids[i].x1 <= x0 && x1 <= cuboids[i].x2)
                    yintervals.push_back({cuboids[i].y1, cuboids[i].y2});
            if (yintervals.empty()) continue;

            sort(yintervals.begin(), yintervals.end());
            long long ylen = 0;
            int cy0 = yintervals[0].first, cy1 = yintervals[0].second;
            for (int j = 1; j < (int)yintervals.size(); j++) {
                if (yintervals[j].first >= cy1) {
                    ylen += cy1 - cy0;
                    cy0 = yintervals[j].first;
                    cy1 = yintervals[j].second;
                } else {
                    cy1 = max(cy1, yintervals[j].second);
                }
            }
            ylen += cy1 - cy0;
            totalVolume += (long long)dz * dx * ylen;
        }
    }

    cout << totalVolume << endl;
    return 0;
}