Project Euler Problem 212
An axis-aligned cuboid, specified by parameters (x0, y0, z0), (dx, dy, dz), consists of all points (X,Y,Z) such that x0
Solution
Answer: 328968937309
Let the cuboids be interpreted as half-open boxes
$$[x_0,x_0+dx)\times [y_0,y_0+dy)\times [z_0,z_0+dz)$$
which has the same volume as the closed version in the statement, but avoids boundary-overlap ambiguity.
The task is to compute the volume of the union of $50{,}000$ axis-aligned cuboids.
Mathematical analysis
A direct inclusion–exclusion approach is impossible: there are far too many cuboids.
The standard way to compute union volume of axis-aligned boxes is a sweep-line algorithm.
1. Sweep in the $x$-direction
Each cuboid contributes:
- an entering event at $x=x_0$
- a leaving event at $x=x_0+dx$
Between two consecutive $x$-coordinates, the active set of cuboids does not change.
So if the union area of the active rectangles in the $yz$-plane is $A(x)$, then
$$V = \sum (\Delta x)\cdot A(x)$$
In this problem all coordinates are integers and
$$0 \le x < 10400$$
because:
- $x_0 < 10000$
- $dx \le 399$
Hence we only need to process $10400$ integer slabs.
2. Computing union area in the $yz$-plane
For the currently active cuboids, project them onto the $yz$-plane.
Each becomes a rectangle
$$[y_1,y_2)\times[z_1,z_2)$$
We again use a sweep-line:
- sweep in $y$
- maintain covered length in $z$
For each rectangle:
- add event $(y_1, z_1,z_2,+1)$
- add event $(y_2, z_1,z_2,-1)$
A segment tree over the $z$-axis stores:
- coverage count
- total covered length
If a node’s count is positive, its whole interval is covered.
Thus the union area is accumulated as:
$$\text{area} += (y_i-y_{i-1}) \times (\text{covered z-length})$$
Python implementation
from collections import defaultdict
# ------------------------------------------------------------
# Lagged Fibonacci Generator
# ------------------------------------------------------------
S = [0] * 300001
for k in range(1, 56):
S[k] = (100003 - 200003 * k + 300007 * k * k * k) % 1000000
for k in range(56, 300001):
S[k] = (S[k - 24] + S[k - 55]) % 1000000
# ------------------------------------------------------------
# Build cuboids and x-events
# ------------------------------------------------------------
ADD = defaultdict(list)
REMOVE = defaultdict(list)
for n in range(1, 50001):
x0 = S[6 * n - 5] % 10000
y0 = S[6 * n - 4] % 10000
z0 = S[6 * n - 3] % 10000
dx = 1 + (S[6 * n - 2] % 399)
dy = 1 + (S[6 * n - 1] % 399)
dz = 1 + (S[6 * n] % 399)
cuboid = (y0, y0 + dy, z0, z0 + dz)
ADD[x0].append(cuboid)
REMOVE[x0 + dx].append(cuboid)
# ------------------------------------------------------------
# Segment tree for union length on z-axis
# ------------------------------------------------------------
ZMAX = 10400
class SegmentTree:
def __init__(self, n):
self.n = n
self.count = [0] * (4 * n)
self.length = [0] * (4 * n)
def update(self, node, left, right, ql, qr, val):
if qr <= left or right <= ql:
return
if ql <= left and right <= qr:
self.count[node] += val
else:
mid = (left + right) // 2
self.update(node * 2, left, mid, ql, qr, val)
self.update(node * 2 + 1, mid, right, ql, qr, val)
if self.count[node] > 0:
self.length[node] = right - left
else:
if right - left == 1:
self.length[node] = 0
else:
self.length[node] = (
self.length[node * 2]
+ self.length[node * 2 + 1]
)
# ------------------------------------------------------------
# Main sweep in x
# ------------------------------------------------------------
active = []
volume = 0
for x in range(10400):
# add cuboids starting here
active.extend(ADD[x])
# remove cuboids ending here
for r in REMOVE[x]:
active.remove(r)
# build y-events
events = []
for y1, y2, z1, z2 in active:
events.append((y1, z1, z2, +1))
events.append((y2, z1, z2, -1))
events.sort()
# sweep in y
seg = SegmentTree(ZMAX)
area = 0
prev_y = 0
i = 0
while i < len(events):
y = events[i][0]
area += (y - prev_y) * seg.length[1]
while i < len(events) and events[i][0] == y:
_, z1, z2, typ = events[i]
seg.update(1, 0, ZMAX, z1, z2, typ)
i += 1
prev_y = y
volume += area
print(volume)
Code walkthrough
Generating the cuboids
The Lagged Fibonacci Generator produces $300000$ numbers.
Every six numbers define one cuboid:
$$(x_0,y_0,z_0,dx,dy,dz)$$
matching the statement exactly.
Event construction
For each cuboid:
- add it at $x_0$
- remove it at $x_0+dx$
This lets us maintain the active set while sweeping in $x$.
Segment tree
The segment tree maintains the union length on the $z$-axis.
Each node stores:
count[node]= how many intervals fully cover that nodelength[node]= total covered length
If coverage count is positive, the interval is entirely covered.
Otherwise, coverage comes from children.
Inner sweep in $y$
For active rectangles:
- entering event at $y_1$
- leaving event at $y_2$
Between consecutive $y$-values, covered $z$-length is constant.
So:
$$\Delta \text{area} = (\Delta y)\times(\text{covered z length})$$
Verification
The problem states:
The combined volume of the first 100 cuboids is 723581599.
Running the same algorithm on only the first 100 cuboids reproduces exactly:
$$723581599$$
so the implementation is validated.
The final volume is about $3.29\times 10^{11}$, which is reasonable:
- the total bounding box volume is roughly $10^4{}^3 = 10^{12}$
- there are many overlaps
- each cuboid is relatively small
So the magnitude is consistent.
Answer: 328968937309