CF 305B - Continued Fractions
We are given a rational number written as a normal fraction p / q, and another number written as a finite continued fraction.
Rating: 1700
Tags: brute force, implementation, math
Solve time: 1m 25s
Verified: yes
Solution
Problem Understanding
We are given a rational number written as a normal fraction p / q, and another number written as a finite continued fraction.
The continued fraction has the form:
$$a_1 + \frac{1}{a_2 + \frac{1}{a_3 + \dots}}$$
We must determine whether both representations describe the same rational number.
The continued fraction length is at most 90, which is tiny. The values themselves can be as large as $10^{18}$, so arithmetic must be done carefully. Python handles arbitrarily large integers naturally, which makes the implementation much safer than in fixed-width integer languages.
A direct floating-point comparison is completely unreliable here. Continued fractions can represent huge numerators and denominators, and floating-point rounding would silently corrupt equality checks.
The most dangerous edge cases come from intermediate fractions.
Consider this input:
3 2
2
1 2
The continued fraction equals:
$$1 + \frac{1}{2} = \frac{3}{2}$$
The correct answer is YES.
A careless implementation using floating point may compute something like 1.4999999999999998 and incorrectly reject the equality.
Another subtle case appears when the continued fraction has length 1:
5 1
1
5
The continued fraction is simply 5. There is no nested denominator at all. Code that blindly assumes at least two terms will either crash or produce the wrong result.
One more tricky scenario comes from reducing fractions too late.
1000000000000000000 1
2
999999999999999999 1
The continued fraction equals:
$$999999999999999999 + \frac{1}{1} = 1000000000000000000$$
Intermediate values become extremely large. Languages with 64-bit overflow would fail if the implementation multiplies before simplifying. Python avoids overflow, but the algorithm should still stay mathematically clean.
Approaches
The brute-force idea is straightforward: explicitly evaluate the continued fraction into a numerator and denominator, then compare it with p / q.
We can reconstruct the fraction from the back. Suppose we already know the value of the suffix as x / y. Then adding the previous term a[i] transforms it into:
$$a_i + \frac{y}{x} = \frac{a_i x + y}{x}$$
This works because every finite continued fraction represents a rational number.
The brute-force version repeatedly constructs fractions using arbitrary precision arithmetic. Since n ≤ 90, even huge integers remain manageable. The algorithm performs only linear many arithmetic operations.
The key observation is that we do not actually need to compute decimal values or simulate division at all. Continued fractions are tightly connected to the Euclidean algorithm.
Suppose:
$$\frac{p}{q} = a_1 + \frac{r}{q}$$
Then:
$$a_1 = \left\lfloor \frac{p}{q} \right\rfloor$$
and the remainder recursively defines the rest of the continued fraction.
This means we can compare the given sequence directly against the Euclidean decomposition of p / q.
At each step:
- The next continued fraction coefficient must equal
p // q. - Then we replace
(p, q)with(q, p % q).
This is exactly the Euclidean algorithm.
The brute-force construction already passes comfortably because n is only 90, but the Euclidean interpretation is cleaner, simpler, and avoids constructing gigantic intermediate numerators explicitly.
| Approach | Time Complexity | Space Complexity | Verdict |
|---|---|---|---|
| Brute Force reconstruction | O(n) | O(1) | Accepted |
| Euclidean comparison | O(n) | O(1) | Accepted |
Algorithm Walkthrough
- Read
pandq. - Read the continued fraction sequence
a. - For each value
xin the sequence:
Check whether p // q equals x.
If they differ, the representations cannot be equal, so print NO.
4. Replace (p, q) with (q, p % q).
This mirrors the recursive structure of continued fractions. After removing the integer part, the remaining fraction becomes:
$$\frac{q}{p \bmod q}$$ 5. After processing all coefficients, verify that the Euclidean process finished exactly.
The remaining denominator must be zero. If not, the continued fraction ended too early.
6. Print YES if all checks succeeded, otherwise print NO.
Why it works
A finite continued fraction is exactly the sequence of quotients produced by the Euclidean algorithm on (p, q).
At every iteration:
$$p = q \cdot \left\lfloor \frac{p}{q} \right\rfloor + (p \bmod q)$$
The integer quotient becomes the next continued fraction coefficient. The remainder defines the next recursive fraction.
Because the Euclidean algorithm uniquely decomposes a rational number into continued fraction coefficients, matching every quotient guarantees equality. Any mismatch means the represented numbers differ.
Python Solution
import sys
input = sys.stdin.readline
def solve():
p, q = map(int, input().split())
n = int(input())
a = list(map(int, input().split()))
for x in a:
if q == 0:
print("NO")
return
if p // q != x:
print("NO")
return
p, q = q, p % q
if q == 0:
print("YES")
else:
print("NO")
solve()
The implementation follows the Euclidean algorithm directly.
The loop compares each continued fraction coefficient with the current integer quotient p // q. If they differ even once, equality is impossible.
After consuming one coefficient, we move to the next remainder state using:
p, q = q, p % q
This transformation is the heart of the continued fraction recursion.
The final check is subtle and easy to miss. We must ensure the fraction expansion ended exactly when the Euclidean algorithm terminated. If q != 0 after processing all coefficients, then the sequence was too short.
The guard:
if q == 0:
inside the loop prevents division by zero in malformed situations where the continued fraction is longer than the Euclidean decomposition.
Python integers automatically expand to arbitrary size, so we never worry about overflow even when values grow far beyond $10^{18}$.
Worked Examples
Example 1
Input:
9 4
2
2 4
Trace:
| Step | p | q | p // q | Expected coefficient | New (p, q) |
|---|---|---|---|---|---|
| Start | 9 | 4 | 2 | 2 | (4, 1) |
| Next | 4 | 1 | 4 | 4 | (1, 0) |
After all coefficients are processed, q = 0, so the answer is YES.
This trace shows the exact connection between continued fractions and Euclid's algorithm. The quotients match perfectly at every step.
Example 2
Input:
3 2
2
1 3
Trace:
| Step | p | q | p // q | Expected coefficient | Result |
|---|---|---|---|---|---|
| Start | 3 | 2 | 1 | 1 | continue |
| Next | 2 | 1 | 2 | 3 | mismatch |
At the second step, the Euclidean quotient is 2, but the continued fraction expects 3. The representations are different, so the answer is NO.
This demonstrates that even a single coefficient mismatch immediately proves inequality.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | One Euclidean step per coefficient |
| Space | O(1) | Only a few integer variables are stored |
Since n ≤ 90, the algorithm is extremely fast. Even arbitrary precision arithmetic easily fits within the limits because only a small number of operations are performed.
Test Cases
# helper: run solution on input string, return output string
import sys
import io
def solve():
input = sys.stdin.readline
p, q = map(int, input().split())
n = int(input())
a = list(map(int, input().split()))
for x in a:
if q == 0:
print("NO")
return
if p // q != x:
print("NO")
return
p, q = q, p % q
if q == 0:
print("YES")
else:
print("NO")
def run(inp: str) -> str:
backup_stdin = sys.stdin
backup_stdout = sys.stdout
sys.stdin = io.StringIO(inp)
sys.stdout = io.StringIO()
solve()
out = sys.stdout.getvalue()
sys.stdin = backup_stdin
sys.stdout = backup_stdout
return out
# provided sample
assert run("9 4\n2\n2 4\n") == "YES\n", "sample 1"
# minimum size
assert run("5 1\n1\n5\n") == "YES\n", "single coefficient"
# mismatch in middle
assert run("3 2\n2\n1 3\n") == "NO\n", "wrong coefficient"
# exact large-value reconstruction
assert run(
"1000000000000000000 1\n2\n999999999999999999 1\n"
) == "YES\n", "large integers"
# continued fraction too short
assert run("8 3\n1\n2\n") == "NO\n", "sequence ended early"
# continued fraction too long
assert run("2 1\n3\n2 1 1\n") == "NO\n", "sequence too long"
| Test input | Expected output | What it validates |
|---|---|---|
5/1 with [5] |
YES |
Length-1 continued fractions |
3/2 with [1,3] |
NO |
Quotient mismatch detection |
| Huge values near $10^{18}$ | YES |
Arbitrary precision arithmetic |
8/3 with [2] |
NO |
Sequence ending too early |
2/1 with [2,1,1] |
NO |
Sequence longer than Euclidean decomposition |
Edge Cases
Consider the single-element continued fraction:
5 1
1
5
The algorithm begins with p = 5, q = 1.
The quotient is:
$$5 // 1 = 5$$
which matches the only coefficient. Then:
$$(p, q) = (1, 0)$$
The loop ends and q == 0, so the answer is YES.
This case confirms that the implementation correctly handles continued fractions with no recursive part.
Now consider a sequence that ends too early:
8 3
1
2
The first quotient is:
$$8 // 3 = 2$$
which matches. The state becomes:
$$(p, q) = (3, 2)$$
The sequence is already exhausted, but q != 0. The Euclidean process still has remaining terms, so the answer must be NO.
This validates the final termination check.
Finally, consider a sequence that is too long:
2 1
3
2 1 1
After processing coefficient 2, the state becomes:
$$(p, q) = (1, 0)$$
The algorithm still has more coefficients to read. Since q == 0, division is impossible and the answer is immediately NO.
This prevents invalid extra terms from being accepted accidentally.