All Euler problems
Project Euler

Sphere Packing

What is the length of the shortest pipe, of internal radius 50 mm, that can fully contain the 21 balls of radii 30, 31, 32,..., 50 mm? Give the answer in micrometres, rounded to the nearest integer.

Source sync May 21, 2026
Problem #0222
Level Level 10
Solved By 2,380
Languages C++, Python
Answer 1590933
Length 316 words
dynamic_programmingoptimizationgeometry

Problem Statement

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

What is the length of the shortest pipe, of internal radius \(\SI {50}{\milli \meter }\), that can fully contain \(21\) balls of radii \(\SI {30}{\milli \meter }, \SI {31}{\milli \meter }, \dots , \SI {50}{\milli \meter }\)?

Give your answer in micrometres (\(10^{-6} m\)) rounded to the nearest integer.

Problem 222: Sphere Packing

Mathematical Development

If two spheres of radii rir_i and rjr_j touch opposite sides of a tube of radius RR, the horizontal distance between their centres is

(R−ri)+(R−rj)=2R−ri−rj.(R-r_i) + (R-r_j) = 2R - r_i - r_j.

Since tangent spheres have centre distance ri+rjr_i + r_j, the axial gap Δ(ri,rj)\Delta(r_i,r_j) satisfies

Δ(ri,rj)2=(ri+rj)2−(2R−ri−rj)2=4R(ri+rj−R),\Delta(r_i,r_j)^2 = (r_i+r_j)^2 - (2R-r_i-r_j)^2 = 4R(r_i+r_j-R),

so

Δ(ri,rj)=2R(ri+rj−R).\Delta(r_i,r_j)=2\sqrt{R(r_i+r_j-R)}.

For an ordering rσ(1),…,rσ(n)r_{\sigma(1)},\dots,r_{\sigma(n)}, the tube length is therefore

L(σ)=rσ(1)+rσ(n)+∑i=1n−1Δ(rσ(i),rσ(i+1)).L(\sigma)=r_{\sigma(1)}+r_{\sigma(n)}+\sum_{i=1}^{n-1}\Delta(r_{\sigma(i)},r_{\sigma(i+1)}).

Now write

g(s)=2R(s−R).g(s)=2\sqrt{R(s-R)}.

The function gg is increasing and concave. Hence for

a≤b≤c≤da \le b \le c \le d

we have the exchange inequality

g(a+c)+g(b+d)≤g(a+d)+g(b+c).g(a+c)+g(b+d)\le g(a+d)+g(b+c).

This says that, with the same total adjacent sum, pairing large radii with small radii is never worse than pairing large with large and small with small.

Applying that exchange repeatedly forces an optimal arrangement into a pendulum order: one parity class is listed from large to small, then the other parity class is listed from small to large. For the radii 30,…,5030,\dots,50, this leaves only two genuinely different candidates:

50,48,46,…,30,31,33,…,4950,48,46,\dots,30,31,33,\dots,49

and

49,47,45,…,31,30,32,…,50.49,47,45,\dots,31,30,32,\dots,50.

Evaluating both gives the optimum.

Editorial

The geometry only contributes one formula, namely the gap

250(ri+rj−50).2\sqrt{50(r_i+r_j-50)}.

After that, the problem is purely about arranging the radii. Because this gap depends on the pair only through ri+rjr_i+r_j, and because the square root is concave, any local pattern that puts two large radii next to each other can be improved by separating them and using the small radii as spacers. That is the observation that kills the 21!21! search.

Once the exchange argument is pushed all the way through, the surviving candidates are the two pendulum orders: one starts with the even radii from 5050 down to 3030 and then climbs through the odd radii, and the other does the same with the parities reversed. The program simply builds those two orders, evaluates the tube length formula, and keeps the smaller value.

Pseudocode

Define the gap formula for two consecutive spheres.

Build the first pendulum order:
    take 50, 48, 46, ..., 30
    then append 31, 33, 35, ..., 49

Build the second pendulum order:
    take 49, 47, 45, ..., 31
    then append 30, 32, 34, ..., 50

For each of the two orders:
    start with the two end-cap contributions
    add the gap between every consecutive pair

Convert the shorter length from millimetres to micrometres.
Round to the nearest integer and print it.

Complexity Analysis

  • Time: O(n)O(n) once the two candidate orders are written down.
  • Space: O(n)O(n) for the order itself.

Answer

1590933\boxed{1590933}

Code

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

C++ project_euler/problem_222/solution.cpp
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <vector>

using namespace std;

double gap(int left, int right, double radius = 50.0) {
    return 2.0 * sqrt(radius * (left + right - radius));
}

double tubeLength(const vector<int>& order) {
    double total = order.front() + order.back();
    for (size_t i = 0; i + 1 < order.size(); i++) {
        total += gap(order[i], order[i + 1]);
    }
    return total;
}

vector<vector<int>> pendulumOrders(const vector<int>& radii) {
    vector<int> sorted = radii;
    sort(sorted.begin(), sorted.end());

    vector<vector<int>> orders;

    for (int parity = 0; parity < 2; parity++) {
        vector<int> first;
        vector<int> second;

        for (auto it = sorted.rbegin(); it != sorted.rend(); ++it) {
            if ((*it & 1) == parity) first.push_back(*it);
        }

        for (int value : sorted) {
            if ((value & 1) != parity) second.push_back(value);
        }

        orders.push_back(first);
        orders.back().insert(orders.back().end(), second.begin(), second.end());
    }

    return orders;
}

int main() {
    vector<int> radii;
    for (int value = 30; value <= 50; value++) {
        radii.push_back(value);
    }

    double best = 1e100;
    for (const auto& order : pendulumOrders(radii)) {
        best = min(best, tubeLength(order));
    }

    cout << llround(best * 1000.0) << '\n';
    return 0;
}