cppAcademy
FROM LEARNING TO COMPETING

Proficient.
Put your skills to the test.

Bulgarian Informatics Group B · 2009–2026

217 independently explained problems: 54 Municipal, 54 Regional and 109 National. Start with the easier tasks and work upwards. Difficulty labels are learning estimates, not official ratings; Group B also includes challenging national-level problems. National-round tasks shared by Groups A and B are included. Years follow Arena’s competition-season labels. Source clarifications and special grader requirements are noted on affected tasks.
GROUP AB2016 · MediumStern-Brocot tree

Common Ancestor of Fractions

National Olympiad — National round (2016 season)

Submission name: rat.cpp

Problem summary

In the Stern–Brocot tree rooted at 1/1, find the fraction at the lowest common ancestor of a node specified by its breadth-first index and a node specified by its reduced fraction.

Input

Index n1, then numerator a2 and denominator b2.

Output

Ancestor numerator and denominator.

Constraints

1 < n1 < 10^18; 0 < a2,b2 < 10^9; gcd(a2,b2)=1; the two specified nodes are different and neither is the root.

Worked example

An independently prepared example for this explanation.

Input

5
1 4

Output

1 2

Solution analysis

  1. The binary expansion of a breadth-first index begins with the root bit 1. Each remaining bit is a tree direction: 0 goes left and 1 goes right. This first node has fewer than 60 directions.
  2. Generate the path toward a2/b2 only as far as those directions. Maintain lower bound 0/1 and upper bound 1/0; their mediant is the current node. Compare the target fraction with the mediant to determine left or right. Stop on equality or at the first differing direction.
  3. Index 5 is binary 101, giving left then right and fraction 2/3. The path to 1/4 begins left then left. Their common prefix is one left move, whose node is 1/2. There is no need to construct a potentially enormous full path to 1/4 or another fraction.

Why it works

A node’s root path uniquely identifies its ancestors, and the deepest shared prefix identifies its lowest common ancestor. Binary index directions recover the first path exactly. Stern–Brocot mediant comparisons recover every required direction of the second path. Stopping at their first divergence or at an endpoint therefore returns precisely the deepest shared ancestor.

Complexity: O(log n1) time and space. At each comparison, the current node is an ancestor of the target fraction, so its numerator and denominator do not exceed the target components. Thus cross-products are below 10^18 and fit signed 64-bit.

Watch out. Do not follow subtractive Euclid all the way to a target with huge depth. Only the first node’s short path can belong to the common prefix.

Commented C++17 solution

An independently written complete program with standard headers.

#include<iostream>
#include<vector>
#include<algorithm>
// Algorithm: The binary expansion of a breadth-first index begins with the root bit 1.
using namespace std;
int main(){
    long long index,numerator,denominator;
    cin>>index>>numerator>>denominator;
    vector<int>directions;
    while(index>1){
        directions.push_back(static_cast<int>(index%2));
        index/=2;
    }
    reverse(directions.begin(),directions.end());
    long long leftNum=0,leftDen=1,rightNum=1,rightDen=0;
    for(int direction:directions){
        long long currentNum=leftNum+rightNum;
        long long currentDen=leftDen+rightDen;
        long long targetSide=numerator*currentDen-denominator*currentNum;
        if(targetSide==0||(targetSide>0)!=(direction==1)){
            break;
        }
        if(direction==0){
            rightNum=currentNum;
            rightDen=currentDen;
        }
        else{
            leftNum=currentNum;
            leftDen=currentDen;
        }
    }
    cout<<leftNum+rightNum<<' '<<leftDen+rightDen<<'\n';
}

Source: Arena Olimpiici ↗