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 Apr 19, 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 nZ+n \in \mathbb{Z}^+, define fn:Z0R0f_n : \mathbb{Z}_{\ge 0} \to \mathbb{R}_{\ge 0} by fn(k)=ek/n1f_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 kkmax:=nln(π+1)k \le k_{\max} := \lfloor n \ln(\pi + 1) \rfloor.

Proof. Since fn(k)0f_n(k) \ge 0 for all k0k \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 knln(π+1)k \le n \ln(\pi + 1). \square

Definition 2. Define the pair set

P={(s,a,b):0abkmax,  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 aba^* \le b^* and cdc^* \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(logP)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[j1].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[j1]\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(PlogP)O(|\mathcal{P}| \log |\mathcal{P}|) dominated by sorting and the binary search sweep. Pair generation is bounded by O(kmax2)O(k_{\max}^2) but the filter sπs \le \pi prunes significantly. For n=10000n = 10000: kmax=14207k_{\max} = 14207 and P7.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;
}