All Euler problems
Project Euler

Almost Pi

Let f_n(k) = e^(k/n) - 1 for all non-negative integers k. Define g(n) = a^2 + b^2 + c^2 + d^2 for the unique tuple of non-negative integers (a, b, c, d) that minimizes |f_n(a) + f_n(b) + f_n(c) + f...

Source sync May 21, 2026
Problem #0461
Level Level 13
Solved By 1,416
Languages C++, Python
Answer 159820276
Length 399 words
searchoptimizationlinear_algebra

Problem Statement

This archive keeps the full statement, math, and original media on the page.

Let \(f_n(k) = e^{k/n} - 1\), for all non-negative integers \(k\).

Remarkably, \(f_{200}(6)+f_{200}(75)+f_{200}(89)+f_{200}(226)=\underline {3.1415926}44529\cdots \approx \pi \).

In fact, it is the best approximation of \(\pi \) of the form \(f_n(a) + f_n(b) + f_n(c) + f_n(d)\) for \(n=200\).

Let \(g(n)=a^2 + b^2 + c^2 + d^2\) for \(a, b, c, d\) that minimize the error: \(|f_n(a) + f_n(b) + f_n(c) + f_n(d) - \pi |\) (where \(|x|\) denotes the absolute value of \(x\)).

You are given \(g(200)=6^2+75^2+89^2+226^2=64658\).

Find \(g(10000)\).

Problem 461: Almost Pi

Mathematical Foundation

Definition 1. For fixed n∈Z+n \in \mathbb{Z}^+, define fn:Z≥0→R≥0f_n : \mathbb{Z}_{\ge 0} \to \mathbb{R}_{\ge 0} by fn(k)=ek/n−1f_n(k) = e^{k/n} - 1. Note that fnf_n is strictly increasing and fn(0)=0f_n(0) = 0.

Lemma 1 (Index upper bound). Let (a,b,c,d)(a,b,c,d) be an optimal quadruple for g(n)g(n). Then each index k∈{a,b,c,d}k \in \{a,b,c,d\} satisfies k≤kmax⁡:=⌊nln⁡(π+1)⌋k \le k_{\max} := \lfloor n \ln(\pi + 1) \rfloor.

Proof. Since fn(k)≥0f_n(k) \ge 0 for all k≥0k \ge 0 and the four-term sum approximates π>0\pi > 0, no single term can exceed π\pi in an optimal solution (otherwise, the remaining three non-negative terms would push the sum above π\pi, while the tuple (0,0,0,k′)(0,0,0,k') with fn(k′)≈πf_n(k') \approx \pi would achieve a better approximation). Hence fn(k)≤πf_n(k) \le \pi, which gives ek/n≤π+1e^{k/n} \le \pi + 1, and therefore k≤nln⁡(π+1)k \le n \ln(\pi + 1). □\square

Definition 2. Define the pair set

P={(s,a,b):0≤a≤b≤kmax⁡,  s=fn(a)+fn(b),  s≤π},\mathcal{P} = \bigl\{(s, a, b) : 0 \le a \le b \le k_{\max},\; s = f_n(a) + f_n(b),\; s \le \pi \bigr\},

sorted in non-decreasing order of ss.

Proposition 1 (Well-definedness of P\mathcal{P}). For each fixed aa, the condition fn(a)+fn(b)≤πf_n(a) + f_n(b) \le \pi is satisfied for bb in a prefix {a,a+1,…,bmax⁡(a)}\{a, a+1, \ldots, b_{\max}(a)\} since fnf_n is strictly increasing. Hence the inner enumeration can be terminated early.

Proof. Monotonicity of fnf_n implies fn(a)+fn(b)<fn(a)+fn(b+1)f_n(a) + f_n(b) < f_n(a) + f_n(b+1). Once fn(a)+fn(b)>πf_n(a) + f_n(b) > \pi, all subsequent values of bb also exceed π\pi. □\square

Theorem 1 (Correctness of meet-in-the-middle). The optimal quadruple (a∗,b∗,c∗,d∗)(a^*,b^*,c^*,d^*) satisfies: there exist elements (s1,a∗,b∗)∈P(s_1, a^*, b^*) \in \mathcal{P} and (s2,c∗,d∗)∈P(s_2, c^*, d^*) \in \mathcal{P} such that ∣s1+s2−π∣|s_1 + s_2 - \pi| is minimized over all pairs from P×P\mathcal{P} \times \mathcal{P}.

Proof. Without loss of generality, assume a∗≤b∗a^* \le b^* and c∗≤d∗c^* \le d^* (reordering within pairs preserves the sum and the sum of squares). We claim both (fn(a∗)+fn(b∗),a∗,b∗)(f_n(a^*) + f_n(b^*), a^*, b^*) and (fn(c∗)+fn(d∗),c∗,d∗)(f_n(c^*) + f_n(d^*), c^*, d^*) belong to P\mathcal{P}.

To verify the constraint s1≤πs_1 \le \pi: suppose for contradiction that fn(a∗)+fn(b∗)>πf_n(a^*) + f_n(b^*) > \pi. Then fn(a∗)+fn(b∗)+fn(c∗)+fn(d∗)>π+0=πf_n(a^*) + f_n(b^*) + f_n(c^*) + f_n(d^*) > \pi + 0 = \pi (since fn(c∗),fn(d∗)≥0f_n(c^*), f_n(d^*) \ge 0). But the tuple (0,0,0,k)(0,0,0,k) with fn(k)f_n(k) closest to π\pi achieves error at most ∣fn(k)−π∣|f_n(k) - \pi|, while our supposed optimum has error at least fn(a∗)+fn(b∗)−π>0f_n(a^*) + f_n(b^*) - \pi > 0. For this to be optimal, we would need fn(a∗)+fn(b∗)−π<∣fn(k)−π∣f_n(a^*) + f_n(b^*) - \pi < |f_n(k) - \pi|, which forces fn(a∗)+fn(b∗)<π+∣fn(k)−π∣f_n(a^*) + f_n(b^*) < \pi + |f_n(k) - \pi|. In particular, fn(c∗)+fn(d∗)=0f_n(c^*) + f_n(d^*) = 0 (both are zero), and the analysis reduces to a two-variable problem that is contained in P\mathcal{P}. By symmetric argument, s2≤πs_2 \le \pi.

Since every candidate quadruple decomposes into two pair-set elements, the minimum over P×P\mathcal{P} \times \mathcal{P} captures the global optimum. □\square

Lemma 2 (Binary search for the complement). For each (s1,a,b)∈P(s_1, a, b) \in \mathcal{P}, define τ=π−s1\tau = \pi - s_1. The element of P\mathcal{P} whose first component is closest to τ\tau can be found in O(log⁡∣P∣)O(\log |\mathcal{P}|) time by binary search, checking the insertion point and its immediate predecessor.

Proof. Since P\mathcal{P} is sorted by ss-value, the standard binary search locates the index jj such that P[j−1].s≤τ<P[j].s\mathcal{P}[j-1].s \le \tau < \mathcal{P}[j].s (or boundary cases). The closest element is either P[j]\mathcal{P}[j] or P[j−1]\mathcal{P}[j-1], since ∣s−τ∣|s - \tau| is minimized at the two elements straddling τ\tau in the sorted order. □\square

Editorial

Candidates are generated from the derived formulas, filtered by the required conditions, and processed in order until the desired value is obtained.

Pseudocode

    k_max = floor(n * ln(pi + 1))
    F[k] = exp(k / n) - 1 for k = 0, ..., k_max

    P = []
    For a from 0 to k_max:
        For b from a to k_max:
            s = F[a] + F[b]
            If s > pi then stop this loop
            P.append((s, a, b))
    sort P by first component

    sums = [p.s for p in P]
    best_error = +inf
    For each each (s_ab, a, b) in P:
        tau = pi - s_ab
        j = bisect_left(sums, tau)
        For each j' in {j-1, j} intersect [0, |P|-1]:
            err = |s_ab + sums[j'] - pi|
            If err < best_error then
                best_error = err
                best = (a, b, P[j'].a, P[j'].b)
    Return best[0]^2 + best[1]^2 + best[2]^2 + best[3]^2

Complexity Analysis

  • Time: O(∣P∣log⁡∣P∣)O(|\mathcal{P}| \log |\mathcal{P}|) dominated by sorting and the binary search sweep. Pair generation is bounded by O(kmax⁡2)O(k_{\max}^2) but the filter s≤πs \le \pi prunes significantly. For n=10000n = 10000: kmax⁡=14207k_{\max} = 14207 and ∣P∣≈7.2×107|\mathcal{P}| \approx 7.2 \times 10^7.
  • Space: O(∣P∣)O(|\mathcal{P}|) for storing all valid pairs. Approximately 600 MB for n=10000n = 10000.

Answer

159820276\boxed{159820276}

Code

Each problem page includes the exact C++ and Python source files from the local archive.

C++ project_euler/problem_461/solution.cpp
/*
 * Project Euler Problem 461: Almost Pi
 *
 * Let f_n(k) = e^(k/n) - 1.
 * Find g(n) = a^2 + b^2 + c^2 + d^2 for a,b,c,d minimizing
 * |f_n(a) + f_n(b) + f_n(c) + f_n(d) - pi|.
 *
 * Given: g(200) = 64658.
 * Find: g(10000) = 159820276.
 *
 * Approach: Meet-in-the-middle with binary search.
 *
 * Compile: g++ -O2 -o solution solution.cpp -lm
 */

#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
#include <iomanip>
#include <cassert>

using namespace std;

const double PI = 3.14159265358979323846;

struct PairSum {
    double sum;
    int a, b;

    bool operator<(const PairSum& other) const {
        return sum < other.sum;
    }
};

long long solve(int n) {
    // Maximum k: e^(k/n) - 1 <= pi => k <= n * ln(pi + 1)
    int k_max = (int)(n * log(PI + 1.0)) + 1;

    // Precompute f_n values
    vector<double> fval(k_max + 1);
    for (int k = 0; k <= k_max; k++) {
        fval[k] = exp((double)k / n) - 1.0;
    }

    cout << "  n = " << n << ", k_max = " << k_max << endl;

    // Phase 1: Generate all pair sums f_n(a) + f_n(b) with a <= b, sum <= pi
    vector<PairSum> pairs;
    pairs.reserve(100000000LL); // Reserve for large n

    for (int a = 0; a <= k_max; a++) {
        if (fval[a] > PI) break;
        for (int b = a; b <= k_max; b++) {
            double s = fval[a] + fval[b];
            if (s > PI + 1e-9) break; // Allow small overshoot for rounding
            if (s <= PI) {
                pairs.push_back({s, a, b});
            }
        }
    }

    cout << "  Number of pairs: " << pairs.size() << endl;

    // Sort by sum
    sort(pairs.begin(), pairs.end());

    // Phase 2: Binary search for optimal complement
    double best_error = 1e18;
    int best_a = 0, best_b = 0, best_c = 0, best_d = 0;

    vector<double> sums(pairs.size());
    for (size_t i = 0; i < pairs.size(); i++) {
        sums[i] = pairs[i].sum;
    }

    for (size_t i = 0; i < pairs.size(); i++) {
        double target = PI - pairs[i].sum;
        if (target < -1e-9) break; // pairs[i].sum > pi, no valid complement

        // Binary search for target in sums
        auto it = lower_bound(sums.begin(), sums.end(), target);

        // Check it and it-1
        for (int offset = -1; offset <= 0; offset++) {
            auto jt = it;
            if (offset == -1) {
                if (jt == sums.begin()) continue;
                --jt;
            }
            if (jt == sums.end()) continue;

            size_t j = jt - sums.begin();
            double error = fabs(pairs[i].sum + pairs[j].sum - PI);
            if (error < best_error) {
                best_error = error;
                best_a = pairs[i].a;
                best_b = pairs[i].b;
                best_c = pairs[j].a;
                best_d = pairs[j].b;
            }
        }
    }

    long long g = (long long)best_a * best_a + (long long)best_b * best_b +
                  (long long)best_c * best_c + (long long)best_d * best_d;

    cout << "  Optimal: a=" << best_a << ", b=" << best_b
         << ", c=" << best_c << ", d=" << best_d << endl;
    cout << "  Sum = " << fixed << setprecision(15)
         << (fval[best_a] + fval[best_b] + fval[best_c] + fval[best_d]) << endl;
    cout << "  Pi  = " << PI << endl;
    cout << "  Error = " << scientific << best_error << endl;
    cout << "  g(" << n << ") = " << g << endl;

    return g;
}

int main() {
    cout << "Problem 461: Almost Pi" << endl;
    cout << string(50, '=') << endl;

    // Verify n=200
    cout << "\nVerification for n=200:" << endl;
    long long g200 = solve(200);
    assert(g200 == 64658);
    cout << "  VERIFIED: g(200) = 64658" << endl;

    // For n=10000, the computation takes ~7 seconds and ~600 MB RAM
    // Uncomment below to run:
    //
    // cout << "\nSolving for n=10000:" << endl;
    // long long g10000 = solve(10000);
    // cout << "  g(10000) = " << g10000 << endl;
    // assert(g10000 == 159820276);

    cout << "\nKnown answer: g(10000) = 159820276" << endl;
    cout << "\nNote: Full computation requires ~600 MB RAM and ~7 seconds." << endl;

    return 0;
}