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...
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

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 bounded above by , the largest axis-aligned square has side length
Proof. A square of side with lower-left corner fits under the hyperbola iff the upper-right corner satisfies . The maximal occurs at equality:
Expanding: . By the quadratic formula:
Since and with , the discriminant and .
Theorem 2 (Binary gap decomposition). Placing a square of side at corner creates exactly two child gaps:
- Right gap: lower-left corner , inheriting index .
- Top gap: lower-left corner , inheriting index .
No other region under the hyperbola is occluded.
Proof. After placing the square , the remaining area under in the original gap splits into:
- The region to the right: , with corner .
- The region above: … but since , for we have iff . The top gap extends from under .
The index of the right gap increments (one more square to the left), and the top gap increments (one more square below).
Lemma 1 (Index pruning). A gap with index can produce a descendant with index only if and . Any gap with or and all its descendants can be discarded.
Proof. The right child increments , the top child increments . Neither operation can decrease an index component. Hence if , no descendant can have ; similarly for .
Theorem 3 (Greedy ordering correctness). Processing gaps in decreasing order of square side length produces the squares in the correct greedy order.
Proof. By definition, is the largest square fitting in any remaining gap after are placed. A max-priority-queue keyed by extracts this maximum at each step. After extraction, the two child gaps are inserted with their respective -values. Since child gaps always yield strictly smaller squares (the domain shrinks), no future gap can produce a square larger than one already extracted.
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: where is the total number of squares processed before the heap is exhausted (or all remaining gaps are pruned). With the pruning, . Each heap operation costs .
- Space: for the priority queue.
Answer
Code
Each problem page includes the exact C++ and Python source files from the local archive.
#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;
}
import heapq
import math
def solve():
"""
Problem 247: Squares Under a Hyperbola
Find the largest n such that the index of S_n is (3,3).
Region: 1 <= x, 0 <= y <= 1/x.
Greedily place largest squares. Index (left, below) counts squares
to the left and below.
"""
def calc_side(x0, y0):
diff = x0 - y0
return (math.sqrt(diff * diff + 4.0) - x0 - y0) / 2.0
# Max-heap via negation: (-side, x0, y0, left, below)
s0 = calc_side(1.0, 0.0)
heap = [(-s0, 1.0, 0.0, 0, 0)]
target_left, target_below = 3, 3
n = 0
last_n = -1
viable = 1 # count of entries in heap with left<=3 and below<=3
while viable > 0 and heap:
neg_s, x0, y0, left, below = heapq.heappop(heap)
s = -neg_s
n += 1
if left <= target_left and below <= target_below:
viable -= 1
if left == target_left and below == target_below:
last_n = n
# Right child
rx, ry = x0 + s, y0
rs = calc_side(rx, ry)
if rs > 1e-15:
heapq.heappush(heap, (-rs, rx, ry, left + 1, below))
if left + 1 <= target_left and below <= target_below:
viable += 1
# Top child
tx, ty = x0, y0 + s
ts = calc_side(tx, ty)
if ts > 1e-15:
heapq.heappush(heap, (-ts, tx, ty, left, below + 1))
if left <= target_left and below + 1 <= target_below:
viable += 1
print(last_n)
solve()