All Euler problems
Project Euler

Squares Under a Hyperbola

Consider the region {(x,y): x >= 1, 0 <= y <= 1/x}. Squares are placed greedily (largest first) into this region. Each square S_n receives an index (a, b): a counts how many squares lie to its left...

Source sync Apr 19, 2026
Problem #0247
Level Level 12
Solved By 1,712
Languages C++, Python
Answer 782252
Length 399 words
greedyalgebrageometry

Problem Statement

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

Consider the region constrained by \(1 \le x\) and \(0 \le y \le 1/x\).

Let \(S_1\) be the largest square that can fit under the curve.

Let \(S_2\) be the largest square that fits in the remaining area, and so on.

Let the index of \(S_n\) be the pair \((\text {left}, \text {below})\) indicating the number of squares to the left of \(S_n\) and the number of squares below \(S_n\).

PIC

The diagram shows some such squares labelled by number.

\(S_2\) has one square to its left and none below, so the index of \(S_2\) is \((1,0)\).

It can be seen that the index of \(S_{32}\) is \((1,1)\) as is the index of \(S_{50}\).

\(50\) is the largest \(n\) for which the index of \(S_n\) is \((1,1)\).

What is the largest \(n\) for which the index of \(S_n\) is \((3,3)\)?

Problem 247: Squares Under a Hyperbola

Mathematical Foundation

Theorem 1 (Maximal inscribed square). In a gap with lower-left corner (x0,y0)(x_0, y_0) bounded above by y=1/xy = 1/x, the largest axis-aligned square has side length

s=(x0y0)2+4(x0+y0)2.s = \frac{\sqrt{(x_0 - y_0)^2 + 4} - (x_0 + y_0)}{2}.

Proof. A square of side ss with lower-left corner (x0,y0)(x_0, y_0) fits under the hyperbola iff the upper-right corner (x0+s,y0+s)(x_0 + s, y_0 + s) satisfies (x0+s)(y0+s)1(x_0 + s)(y_0 + s) \le 1. The maximal ss occurs at equality:

(x0+s)(y0+s)=1.(x_0 + s)(y_0 + s) = 1.

Expanding: s2+s(x0+y0)+x0y01=0s^2 + s(x_0 + y_0) + x_0 y_0 - 1 = 0. By the quadratic formula:

s=(x0+y0)+(x0+y0)24(x0y01)2=(x0y0)2+4(x0+y0)2.s = \frac{-(x_0 + y_0) + \sqrt{(x_0 + y_0)^2 - 4(x_0 y_0 - 1)}}{2} = \frac{\sqrt{(x_0 - y_0)^2 + 4} - (x_0 + y_0)}{2}.

Since x01x_0 \ge 1 and y00y_0 \ge 0 with x0y01x_0 y_0 \le 1, the discriminant (x0y0)2+4>0(x_0 - y_0)^2 + 4 > 0 and s>0s > 0. \square

Theorem 2 (Binary gap decomposition). Placing a square of side ss at corner (x0,y0)(x_0, y_0) creates exactly two child gaps:

  1. Right gap: lower-left corner (x0+s,y0)(x_0 + s, y_0), inheriting index (a+1,b)(a+1, b).
  2. Top gap: lower-left corner (x0,y0+s)(x_0, y_0 + s), inheriting index (a,b+1)(a, b+1).

No other region under the hyperbola is occluded.

Proof. After placing the square [x0,x0+s]×[y0,y0+s][x_0, x_0+s] \times [y_0, y_0+s], the remaining area under y=1/xy = 1/x in the original gap splits into:

  • The region to the right: {(x,y):xx0+s,  y0y1/x}\{(x,y) : x \ge x_0 + s,\; y_0 \le y \le 1/x\}, with corner (x0+s,y0)(x_0+s, y_0).
  • The region above: {(x,y):x0xx0+s,  y0+sy1/x}\{(x,y) : x_0 \le x \le x_0 + s,\; y_0 + s \le y \le 1/x\}… but since (x0+s)(y0+s)=1(x_0+s)(y_0+s) = 1, for x[x0,x0+s]x \in [x_0, x_0+s] we have 1/xy0+s1/x \ge y_0 + s iff xx0+sx \le x_0 + s. The top gap extends from (x0,y0+s)(x_0, y_0+s) under y=1/xy = 1/x.

The index of the right gap increments aa (one more square to the left), and the top gap increments bb (one more square below). \square

Lemma 1 (Index pruning). A gap with index (a,b)(a, b) can produce a descendant with index (3,3)(3, 3) only if a3a \le 3 and b3b \le 3. Any gap with a>3a > 3 or b>3b > 3 and all its descendants can be discarded.

Proof. The right child increments aa, the top child increments bb. Neither operation can decrease an index component. Hence if a>3a > 3, no descendant can have a=3a = 3; similarly for bb. \square

Theorem 3 (Greedy ordering correctness). Processing gaps in decreasing order of square side length ss produces the squares S1,S2,S_1, S_2, \ldots in the correct greedy order.

Proof. By definition, SnS_n is the largest square fitting in any remaining gap after S1,,Sn1S_1, \ldots, S_{n-1} are placed. A max-priority-queue keyed by ss extracts this maximum at each step. After extraction, the two child gaps are inserted with their respective ss-values. Since child gaps always yield strictly smaller squares (the domain shrinks), no future gap can produce a square larger than one already extracted. \square

Editorial

We max-heap keyed by square side length. We then initial gap: corner (1, 0), index (0, 0). Finally, generate right child: (x0+s, y0) with index (a+1, b).

Pseudocode

Max-heap keyed by square side length
Initial gap: corner (1, 0), index (0, 0)
while heap is not empty
Generate right child: (x0+s, y0) with index (a+1, b)
Generate top child: (x0, y0+s) with index (a, b+1)

Complexity Analysis

  • Time: O(NlogN)O(N \log N) where NN is the total number of squares processed before the heap is exhausted (or all remaining gaps are pruned). With the (3,3)(3,3) pruning, N782,252N \approx 782{,}252. Each heap operation costs O(logN)O(\log N).
  • Space: O(N)O(N) for the priority queue.

Answer

782252\boxed{782252}

Code

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

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

int main() {
    // Problem 247: Find the largest n such that S_n has index (3,3)
    // Region: 1 <= x, 0 <= y <= 1/x
    // Greedily place largest square first

    auto calcSide = [](double x0, double y0) -> double {
        double diff = x0 - y0;
        return (sqrt(diff * diff + 4.0) - x0 - y0) / 2.0;
    };

    struct Square {
        double side, x, y;
        int left, below;
        bool operator<(const Square& o) const {
            return side < o.side; // min-heap: smallest on top
        }
    };

    priority_queue<Square> pq;
    double s0 = calcSide(1.0, 0.0);
    pq.push({s0, 1.0, 0.0, 0, 0});

    int targetLeft = 3, targetBelow = 3;
    int n = 0, lastN = -1;
    int viable = 1;

    while (viable > 0 && !pq.empty()) {
        Square sq = pq.top();
        pq.pop();
        n++;

        if (sq.left <= targetLeft && sq.below <= targetBelow)
            viable--;

        if (sq.left == targetLeft && sq.below == targetBelow)
            lastN = n;

        // Right child
        double rx = sq.x + sq.side, ry = sq.y;
        double rs = calcSide(rx, ry);
        if (rs > 1e-15) {
            pq.push({rs, rx, ry, sq.left + 1, sq.below});
            if (sq.left + 1 <= targetLeft && sq.below <= targetBelow)
                viable++;
        }

        // Top child
        double tx = sq.x, ty = sq.y + sq.side;
        double ts = calcSide(tx, ty);
        if (ts > 1e-15) {
            pq.push({ts, tx, ty, sq.left, sq.below + 1});
            if (sq.left <= targetLeft && sq.below + 1 <= targetBelow)
                viable++;
        }
    }

    cout << lastN << endl;
    return 0;
}