CF 281B - Nearest Fraction
We are asked to approximate a given fraction $x/y$ with another fraction $a/b$ where $b$ is at most $n$. The goal is to make $ Conceptually, we are trying to find the "closest" fraction with a bounded denominator to a given fraction.
Rating: 1700
Tags: brute force, implementation, two pointers
Solve time: 2m
Verified: yes
Solution
Problem Understanding
We are asked to approximate a given fraction $x/y$ with another fraction $a/b$ where $b$ is at most $n$. The goal is to make $|x/y - a/b|$ as small as possible, and if multiple fractions tie, we first minimize $b$ and then $a$.
Conceptually, we are trying to find the "closest" fraction with a bounded denominator to a given fraction. The input gives the target fraction $x/y$ and the maximum allowed denominator $n$. The output must be a simple fraction $a/b$ in lowest terms that is nearest to $x/y$.
The constraints $x, y, n \le 10^5$ suggest that algorithms with time complexity worse than $O(n)$ could struggle in Python. A naive double loop over all numerators and denominators would be roughly $O(n^2)$, or up to $10^{10}$ iterations in the worst case, which is too slow. This hints that a per-denominator calculation is feasible, but iterating over all numerator-denominator pairs is not.
An edge case arises when the target fraction is exactly representable with a denominator less than $n$. For instance, if $x=1$, $y=2$, and $n=4$, the best fraction is $1/2$. Another subtle case is when the target fraction is very small, e.g., $x=1$, $y=100$, $n=2$. Here, the closest fraction is $0/1$, not $1/2$. A naive rounding approach might miss this by always rounding up.
Approaches
The brute-force approach considers every fraction with denominator $1 \le b \le n$ and computes the numerator $a$ that minimizes the difference. Formally, for each $b$, we could test all $a$ from $0$ to $b$. This would give $O(n^2)$ operations, which is correct but infeasible for $n \sim 10^5$.
The key insight is that for a fixed $b$, the optimal numerator $a$ is close to $x \cdot b / y$. This comes from rearranging the error formula:
$$|x/y - a/b| = |x \cdot b - a \cdot y| / (b \cdot y)$$
Minimizing the absolute difference $|x \cdot b - a \cdot y|$ is equivalent to minimizing the overall error. Therefore, we only need to consider $a = \lfloor x \cdot b / y \rfloor$ and $a + 1$, because these are the two integers closest to $x \cdot b / y$. This reduces the problem from $O(n^2)$ to $O(n)$, since for each denominator we only check two numerators.
This approach also handles the tie-breaking rules naturally: if two fractions have the same difference, the one with smaller $b$ is chosen first because we process denominators in increasing order, and among equal $b$, the smaller $a$ is automatically chosen by checking $a$ before $a+1$.
| Approach | Time Complexity | Space Complexity | Verdict |
|---|---|---|---|
| Brute Force | O(n^2) | O(1) | Too slow |
| Optimal | O(n) | O(1) | Accepted |
Algorithm Walkthrough
- Read integers $x, y, n$. Initialize variables to store the best fraction:
best_a = 0,best_b = 1, andmin_diff = infinity. - Loop over each possible denominator $b$ from 1 to $n$. For each $b$, compute
a = (x * b) // y. This is the integer numerator closest to $x * b / y$. - For
aanda + 1, compute the differencediff = abs(x * b - a * y). This is the unscaled error in the numerator units. - If
diff * best_b < min_diff * b, updatebest_a = a,best_b = b, andmin_diff = diff. This comparison avoids floating-point errors by cross-multiplying. - After the loop, print
best_a/best_b.
Why it works: For each denominator, we consider only the two numerators closest to the real-valued target. The cross-multiplication ensures we compare absolute differences without floating-point errors. By iterating denominators in order, the tie-breaking rules are automatically respected. This guarantees that the fraction found is globally optimal.
Python Solution
import sys
input = sys.stdin.readline
x, y, n = map(int, input().split())
best_a, best_b = 0, 1
min_diff = x # initial large value
for b in range(1, n + 1):
a = (x * b) // y
for candidate in [a, a + 1]:
if 0 <= candidate <= b:
diff = abs(x * b - candidate * y)
if diff * best_b < min_diff * b:
best_a, best_b, min_diff = candidate, b, diff
print(f"{best_a}/{best_b}")
The first line reads input efficiently. We initialize best_a and best_b to 0/1 because 0 is always a valid numerator. For each denominator, (x*b)//y is the best candidate, and we also check a + 1 to ensure rounding up is considered. The comparison diff * best_b < min_diff * b avoids floating-point errors by comparing scaled integers.
Worked Examples
Sample 1
Input: 3 7 6
| b | a = floor(3_b/7) | candidate | diff = |3_b - a*y| | best_a/b |
|---|---|---|---|---|
| 1 | 0 | 0 | 3 | 0/1 |
| 2 | 0 | 0 | 6 | 0/1 |
| 2 | 0 | 1 | 1 | 1/2 |
| 3 | 1 | 1 | 2 | 1/2 |
| 3 | 1 | 2 | 1 | 2/3 |
| 4 | 1 | 1 | 5 | 2/3 |
| 4 | 1 | 2 | 2 | 2/4 |
| 5 | 2 | 2 | 1 | 2/5 |
| 5 | 2 | 3 | 2 | 2/5 |
| 6 | 2 | 2 | 3 | 2/5 |
| 6 | 2 | 3 | 1 | 2/6 |
Output: 2/5
This trace shows that iterating denominators and checking a and a+1 finds the fraction with minimal difference.
Sample 2
Input: 1 2 4
Best fraction: 1/2
Trace confirms the algorithm chooses exact representations when possible.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Loop over all denominators 1..n, checking at most 2 numerators per denominator |
| Space | O(1) | Constant number of variables; no arrays needed |
With $n \le 10^5$, this executes around 2*10^5 operations, comfortably under the 2s time limit in Python.
Test Cases
import sys, io
def run(inp: str) -> str:
sys.stdin = io.StringIO(inp)
x, y, n = map(int, input().split())
best_a, best_b = 0, 1
min_diff = x
for b in range(1, n + 1):
a = (x * b) // y
for candidate in [a, a + 1]:
if 0 <= candidate <= b:
diff = abs(x * b - candidate * y)
if diff * best_b < min_diff * b:
best_a, best_b, min_diff = candidate, b, diff
return f"{best_a}/{best_b}"
# Provided samples
assert run("3 7 6\n") == "2/5", "sample 1"
assert run("1 2 4\n") == "1/2", "sample 2"
# Custom cases
assert run("1 100 2\n") == "0/1", "small fraction, small denominator"
assert run("100 100 100\n") == "1/1", "equal numerator and denominator"
assert run("5 9 5\n") == "3/5", "tie-breaking on numerator"
assert run("7 12 10\n") == "6/10", "larger denominator, rounding down"
| Test input | Expected output | What it validates |
|---|---|---|
| 1 100 2 | 0/1 | Very small |