Farey Sequence Problem

Revision en2, by YipChip, 2026-05-27 17:14:22

First, the Farey sequence $$$F_n$$$ represents the set of all completely reduced proper fractions with denominators not exceeding $$$n$$$, arranged in ascending order of value. Formally speaking,

$$$F_n = \left\{\frac{p}{q} \bigg\vert 0 \lt p \lt q \le n, \, \gcd(p, q) = 1\right\}$$$

The asymptotic size of this sequence is $$$\sum\limits_{i = 1}^{n} \varphi(n) \sim \frac{3}{\pi^2}n^2 + o(n \log n)$$$.

Regarding the Farey sequence, there are two classic conclusions:

  • If $$$\frac{a}{b}$$$ and $$$\frac{c}{d}$$$ are two consecutive elements in $$$F_n$$$, then $$$\frac{a + c}{b + d}$$$ is also a valid Farey fraction.

  • If $$$\frac{a}{b}$$$ and $$$\frac{c}{d}$$$ are two consecutive elements in $$$F_n$$$, then their next element $$$\frac{p}{q}$$$ satisfies

$$$\begin{cases}p = \left\lfloor\frac{n + b}{d}\right\rfloor c - a \\ q = \left\lfloor\frac{n + b}{d}\right\rfloor d - b\end{cases}$$$

We will only prove the second theorem above. For the Farey sequence of order $$$n$$$, two adjacent terms $$$\frac{a}{b} \lt \frac{c}{d}$$$ satisfy $$$bc - ad = 1$$$. Since the adjacent terms $$$\frac{a}{b}, \, \frac{c}{d}$$$ are both on the Stern-Brocot tree, the next term $$$\frac{p}{q}$$$ satisfies $$$\frac{c}{d} = \frac{a + p}{b + q}$$$, which means $$$(p + a)d = (q + b)c$$$. Therefore, there exists $$$k$$$ such that

$$$\begin{cases} kc = a + p \\ kd = b + q \end{cases}$$$

To make $$$p, \, q$$$ highly precise (i.e., closely packed), the value of $$$k$$$ should be as large as possible. Therefore, concerning the upper bound $$$n$$$, $$$p, \, q$$$ are defined by the following equations:

$$$\begin{cases}p = kc - a \le n \\ q = kd - b \le n\end{cases}$$$

Obviously, from the definition of a proper fraction, we know $$$q \gt p$$$, so the maximum $$$k = \left\lfloor\frac{n + b}{d}\right\rfloor$$$, thus

$$$\begin{cases}p = \left\lfloor\frac{n + b}{d}\right\rfloor c - a \\ q = \left\lfloor\frac{n + b}{d}\right\rfloor d - b\end{cases}$$$

There are two computational methods to generate this sequence. The first one is based on the Stern-Brocot tree from the first theorem, which is very easy to implement.

int build(int a, int b, int c, int d, int n) {
    if (b + d > n) return 0;
    return 1 + build(a, b, a + c, b + d, n) + build(a + c, b + d, c, d, n);
}

The other one is based on the recurrence from the second theorem, which is also quite easy to write.

PII nxt_fraction(PII fac1, PII fac2) {
    auto [a, b] = fac1;
    auto [c, d] = fac2;
    return {(n + b) / d * c - a, (n + b) / d * d - b};
}

Obviously, they both use an $$$O(n^2)$$$ time complexity to compute specific elements of the Farey sequence.

To facilitate solving the parent problem, we present the following two problems:

Given $$$n, \, k$$$, find the $$$k$$$-th term of $$$F_n$$$.

Given $$$n$$$ and a completely reduced proper fraction $$$\frac{p}{q} (p \le n)$$$, determine its rank in $$$F_n$$$.

Let's consider the first problem first. We find that the first problem can be processed through the following steps:

  • Binary search for $$$j$$$ in $$$\frac{j}{n}$$$, and calculate $$$\text{rank}(\frac{j}{n})$$$

    • Let $$$r = \text{rank}(\frac{j}{n})$$$. If $$$r \lt k$$$, search upwards; otherwise, search downwards.
  • Find an interval $$$\left[\frac{j}{n}, \, \frac{j + 1}{n}\right)$$$ such that the target fraction falls within this range.

  • Count the fractions within this interval and find a valid fraction such that its rank equals the given $$$k$$$.

    • Note that there is at most one fraction for each different denominator within this interval, because $$$\frac{1}{n}$$$ is the minimum spacing.
    • For a fraction with a denominator $$$q$$$, the numerator of the only possible fraction can only be $$$\left\lfloor\frac{(j + 1)q - 1}{n}\right\rfloor$$$.
  • Find the smallest fraction $$$\frac{p}{q}$$$ strictly greater than $$$\frac{j}{n}$$$, and use the recurrence from conclusion two until the given fraction reaches rank $$$k$$$.

We can see that problem one can be transformed into problem two to be implemented. Therefore, we now consider how to accomplish problem two.

More trivially, problem two can be reduced to the following problem:

Given a real number $$$x$$$, find the number of completely reduced proper fractions $$$\frac{p}{q} \le x$$$ with denominators $$$q \le n$$$.

Let $$$A_q$$$ denote the number of completely reduced proper fractions with denominator $$$q$$$ that satisfy the above conditions. Obviously, the original problem can be expressed as finding such a set:

$$$S = \left\{\frac{p}{q} \bigg\vert \frac{p}{q} \le x, \, q \le n, \, \gcd(p, \, q) = 1\right\}$$$

That is, calculating $$$\sum\limits_{i = 1}^{n}A_i = |S|$$$, and we know that

$$$\left\lfloor x \cdot q \right\rfloor = \sum\limits_{d \mid q}A_d$$$

This tells us that

$$$A_q = \left\lfloor x \cdot q \right\rfloor - \sum\limits_{d \mid q, \, d \lt q}A_d$$$

We can simply compute this iteratively in ascending order. The complexity of each calculation is $$$O(n\log{n})$$$, making the complexity of problem one $$$O(n\log^2{n})$$$. Is there a more optimal approach?

Considering the essential form of $$$A_q$$$, by utilizing Möbius inversion, we can obtain

$$$A_q = \sum\limits_{i = 1}^{\lfloor x \cdot q \rfloor}[\gcd(i, q) = 1] = \sum\limits_{i = 1}^{\lfloor x \cdot q \rfloor}\sum\limits_{d \mid \gcd(i, q)}\mu(d)$$$

If we sum up all $$$A_q$$$, then we have

$$$\begin{aligned} |S| &= \sum_{i = 1}^{n}A_i \\ &= \sum_{i = 1}^{n}\sum_{j = 1}^{\lfloor x \cdot i \rfloor}[\gcd(i, \, j) = 1] \\ &= \sum_{i = 1}^{n}\sum_{j = 1}^{\lfloor x \cdot i \rfloor}\sum_{d \mid \gcd(i, j)}\mu(d) \\ &= \sum_{d \mid i, \, d \mid j}\sum_{i = 1}^{n}\sum_{j = 1}^{\lfloor x \cdot i \rfloor}\mu(d) \\ &= \sum_{d = 1}^{n}\sum_{i = 1}^{\left\lfloor\frac{n}{d}\right\rfloor}\sum_{j = 1}^{\lfloor x \cdot i \rfloor}\mu(d) \\ &= \sum_{d = 1}^{n}\mu(d)\sum_{i = 1}^{\left\lfloor\frac{n}{d}\right\rfloor}\lfloor x \cdot i \rfloor \end{aligned}$$$

Consider setting $$$S(n) = \sum\limits_{i = 1}^{n}\lfloor x \cdot i \rfloor$$$, then what we equivalently need to calculate is

$$$\sum_{d = 1}^{n}\mu(d)S\left(\left\lfloor\frac{n}{d}\right\rfloor\right)$$$

By calculating the prefix sums of $$$\lfloor x \cdot i \rfloor$$$ and precomputing the Möbius function, we can solve this problem in $$$O(n)$$$ time complexity.

Thus, in solving Farey sequence problems, we have:

  • Precomputation: $$$O(n)$$$

  • Finding a fraction given its $$$\text{rank}$$$: $$$O(n\log{n})$$$

  • Finding the $$$\text{rank}$$$ given a fraction: $$$O(n)$$$

  • Finding the number of elements in $$$F_n$$$ smaller than a certain real number: $$$O(n)$$$

  • Rational approximation of positive real numbers: $$$O(n\log{n})$$$

We provide a template code for reference.

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define PII pair<ll, ll>
const int N = 2e6 + 10;
ll n;
ll primes[N], st[N], mu[N], cnt;

// precalculate Mu
void init_mu() {
    mu[1] = 1;
    for (int i = 2; i < N; i ++ ) {
        if (!st[i]) primes[cnt ++ ] = i, mu[i] = -1;
        for (int j = 0; i * primes[j] < N; j ++ ) {
            st[i * primes[j]] = 1;
            if (i % primes[j] == 0) break;
            mu[i * primes[j]] = -mu[i];
        }
    }
}

// O(n + logn) calculate next one by one
PII nxt_fraction(PII fac) {
    ll l = 0, r = n;
    while (l < r) {
        ll mid = l + r + 1 >> 1;
        if (mid * fac.second <= n * fac.first) l = mid;
        else r = mid - 1;
    }
    PII res = {r + 1, n};
    for (int i = 1; i <= n; i ++ ) {
        PII fac_i = {((r + 1) * i - 1) / n, i};
        if (fac_i.first * fac.second <= fac_i.second * fac.first) continue;
        if (fac_i.first * res.second < fac_i.second * res.first) res = fac_i;
    }
    ll d = __gcd(res.first, res.second);
    return {res.first / d, res.second / d};
}

// O(1) calculate next one by two nearly
PII nxt_fraction(PII fac1, PII fac2) {
    auto [a, b] = fac1;
    auto [c, d] = fac2;
    return {(n + b) / d * c - a, (n + b) / d * d - b};
}

ll fraction_to_rank(PII fac) {
    static ll A[N];
    for (int i = 1; i <= n; i ++ ) A[i] = fac.first * i / fac.second + A[i - 1];
    ll res = 0;
    for (int i = 1; i <= n; i ++ ) res += mu[i] * A[n / i];
    return res;
}

PII rank_to_fraction(ll k) {
    ll l = 0, r = n;
    while (l < r) {
        ll mid = l + r + 1 >> 1;
        if (fraction_to_rank({mid, n}) <= k) l = mid;
        else r = mid - 1;
    }
    k -= fraction_to_rank({r, n});
    PII a = {r / __gcd(r, n), n / __gcd(r, n)}, b = nxt_fraction(a);
    if (!k) return a;
    while (k -- ) a = nxt_fraction(a, b), swap(a, b);
    return a;
}

void solve() {
    cin >> n;
    PII now = {1, n};
    while (now.first != now.second) {
        ll rk = fraction_to_rank(now);
        PII fac = rank_to_fraction(rk);
        cout << rk << " : " << fac.first << " " << fac.second << " | " << now.first << " " << now.second << "\n";
        now = nxt_fraction(now);
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    init_mu();
    int T = 1;
    // cin >> T;
    while (T -- ) solve();
    return 0;
}

Expansion

Knowing one term, the time complexity of solving for the next term is $$$O(n + \log{n})$$$. To find the closest fraction $$$\frac{c}{d}$$$ that satisfies $$$bc - ad = 1$$$, we can use the Extended Euclidean Algorithm. Since the difference between two adjacent terms is $$$\frac{1}{bd}$$$, we only need to maximize $$$d$$$.

On the other hand, it is easy to see that we rarely encounter situations where $$$x$$$ is an irrational number in practical problems. Therefore, we can naturally assume $$$x = \frac{p}{q}$$$, giving us

$$$\sum\limits_{d = 1}^{n}\mu(d)\sum\limits_{i = 1}^{\left\lfloor\frac{n}{d}\right\rfloor}\lfloor x \cdot i \rfloor = \sum\limits_{d = 1}^{n}\mu(d)\sum\limits_{i = 1}^{\left\lfloor\frac{n}{d}\right\rfloor}\left\lfloor \frac{pi}{q} \right\rfloor$$$

It is easy to see that the latter is the standard form for our Euclidean-like algorithm. By using number theory block division (square root decomposition), we can achieve $$$O(\sqrt{n}\log^2{n})$$$ per query.

Furthermore, you can use the Du Sieve to sum the preceding Möbius function, reducing the precomputation time complexity to $$$O(n^{\frac{2}{3}})$$$.

If you still feel this method is not optimal, you can utilize Dirichlet prefix sums with block processing, achieving an astonishing overall time complexity of $$$O(n^{\frac{2}{3}} + \sqrt{n}\log^{\frac{3}{2}}{n})$$$.

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define PII pair<ll, ll>
const int N = 1e5;
ll n, k;
ll primes[N], st[N], mu[N], cnt;
unordered_map<ll, ll> summu;

// precalculate Mu
void init_mu() {
    mu[1] = 1;
    for (int i = 2; i < N; i ++ ) {
        if (!st[i]) primes[cnt ++ ] = i, mu[i] = -1;
        for (int j = 0; i * primes[j] < N; j ++ ) {
            st[i * primes[j]] = 1;
            if (i % primes[j] == 0) break;
            mu[i * primes[j]] = -mu[i];
        }
    }
    for (int i = 2; i < N; i ++ ) mu[i] += mu[i - 1];
}

ll get_mu(ll n) {
    if (n < N) return mu[n];
    if (summu.count(n)) return summu[n];
    ll res = 1;
    for (ll l = 2, r; l <= n; l = r + 1) {
        r = n / (n / l);
        res -= (r - l + 1) * get_mu(n / l);
    }
    return summu[n] = res;
}

ll f(ll a, ll b, ll c, ll n) {
	if (!a) return b / c * (n + 1);
	if (a < c && b < c) {
		ll m = (a * n + b) / c;
		if (!m) return 0;
		return n * m - f(c, c - b - 1, a, m - 1);
	}
	return f(a % c, b % c, c, n) + (n + 1) * n / 2 * (a / c) + (n + 1) * (b / c);
}

// O(logn) calculate next one by one
PII nxt_fraction(ll n, PII fac) {
    ll a = fac.first, b = fac.second, c, d;
    exgcd(b, a, c, d);
    d = -d;
    if (d <= 0) {
        ll k = (-d + b - 1) / b;
        c += k * a;
        d += k * b;
    }
    ll k = (n - d) / b;
    return {c + k * a, d + k * b};
}

// O(1) calculate next one by two nearly
PII nxt_fraction(ll n, PII fac1, PII fac2) {
    auto [a, b] = fac1;
    auto [c, d] = fac2;
    return {(n + b) / d * c - a, (n + b) / d * d - b};
}

// O(sqrt(n)logn) calculate fraction's rank
ll fraction_to_rank(ll n, PII fac) {
    ll res = 0;
    for (ll l = 1, r; l <= n; l = r + 1) {
        r = n / (n / l);
        res += (get_mu(r) - get_mu(l - 1)) * f(fac.first, 0, fac.second, n / l);
    }
    return res;
}

// O(sqrt(n)log(n)^2) calculate the fraction of k-th rank
PII rank_to_fraction(ll n, ll k) {
    ll l = 0, r = n;
    while (l < r) {
        ll mid = l + r + 1 >> 1;
        if (fraction_to_rank(n, {mid, n}) <= k) l = mid;
        else r = mid - 1;
    }
    k -= fraction_to_rank(n, {r, n});
    PII a = {r / __gcd(r, n), n / __gcd(r, n)}, b = nxt_fraction(n, a);
    if (!k) return a;
    while (k -- ) a = nxt_fraction(n, a, b), swap(a, b);
    return a;
}

void solve() {
    cin >> n;
    PII now = {1, n};
    while (now.first != now.second) {
        ll rk = fraction_to_rank(n, now);
        PII fac = rank_to_fraction(n, rk);
        cout << rk << " : " << fac.first << " " << fac.second << " | " << now.first << " " << now.second << "\n";
        now = nxt_fraction(n, now);
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    init_mu();
    int T = 1;
    // cin >> T;
    while (T -- ) solve();
    return 0;
}
Tags farey, math, analysis, number theory

History

 
 
 
 
Revisions
 
 
  Rev. Lang. By When Δ Comment
en2 English YipChip 2026-05-27 17:14:22 58
en1 English YipChip 2026-05-27 17:13:11 13390 Initial revision (published)