All Euler problems
Project Euler

Project Euler Problem 408: Admissible Paths Through a Grid

A lattice point (x, y) is called inadmissible if x, y, and x + y are all positive perfect squares. For example, (9, 16) is inadmissible because 9 = 3^2, 16 = 4^2, and 9 + 16 = 25 = 5^2. A path from...

Source sync Apr 19, 2026
Problem #0408
Level Level 19
Solved By 691
Languages C++, Python
Answer 299742733
Length 561 words
modular_arithmeticgeometrydynamic_programming

Problem Statement

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

Let’s call a lattice point \((x, y)\) inadmissible if \(x\), \(y\) and \(x + y\) are all positive perfect squares

For example, \((9, 16)\) is inadmissible, while \((0, 4)\), \((3, 1)\) and \((9, 4)\) are not.

Consider a path from point \((x_1, y_1)\) to point \((x_2, y_2)\) using only unit steps north or east.

Let’s call such a path admissible if noe of its intermediate points are inadmissible.

Let \(P(n)\) be the number of admissible paths from \((0, 0)\) to \((n, n)\).

It can be verified that \(P(5) = 252\), \(P(16) = 596994440\) and \(P(1000) \mod \num {1000000007} = 341920854\).

Find \(P(\num {10000000}) \mod \num {1000000007}\).

Project Euler Problem 408: Admissible Paths Through a Grid

Mathematical Analysis

Finding Inadmissible Points

A point (x, y) is inadmissible if x = a^2, y = b^2, and a^2 + b^2 = c^2 for positive integers a, b, c. These are Pythagorean triples!

So inadmissible points within [0, n] x [0, n] correspond to Pythagorean triples (a, b, c) with a^2 <= n and b^2 <= n.

Pythagorean Triple Generation

All primitive Pythagorean triples (a, b, c) are given by:

  • a = m^2 - n^2, b = 2mn, c = m^2 + n^2 (or with a and b swapped) where m > n > 0, gcd(m,n) = 1, m - n odd.

All triples are multiples of primitive ones: (ka, kb, kc).

The inadmissible point is ((ka)^2, (kb)^2) = (k^2 a^2, k^2 b^2).

Inclusion-Exclusion on Paths

Total paths from (0,0) to (n,n) = C(2n, n).

For inadmissible points, we use inclusion-exclusion. Let the set of inadmissible points be {p1, p2, …, pm} sorted in a compatible order. The number of admissible paths equals:

P(n) = sum over subsets S of inadmissible points of (-1)^|S| * (product of binomial coefficients for paths through those points)

This can be computed efficiently using a DP approach: for each inadmissible point in sorted order, compute the number of “bad” paths through it that avoid all previously processed inadmissible points.

Efficient Computation

Sort inadmissible points by (x+y, x). For each point p_i = (x_i, y_i):

  • Let f(i) = number of paths from (0,0) to (x_i, y_i) that pass through no other inadmissible point.
  • f(i) = C(x_i + y_i, x_i) - sum over j < i with x_j <= x_i, y_j <= y_i of f(j) * C(x_i - x_j + y_i - y_j, x_i - x_j)

Then P(n) = C(2n, n) - sum over all i of f(i) * C(2n - x_i - y_i, n - x_i).

Editorial

Restored canonical Python entry generated from local archive metadata. We generate all Pythagorean triples (a, b, c) with a^2, b^2 <= n = 10^7. We then collect inadmissible points (a^2, b^2) and (b^2, a^2). Finally, sort points by (x+y, x).

Pseudocode

Generate all Pythagorean triples (a, b, c) with a^2, b^2 <= n = 10^7
Collect inadmissible points (a^2, b^2) and (b^2, a^2)
Sort points by (x+y, x)
DP with inclusion-exclusion to compute P(n) mod 10^9+7
Precompute factorials and inverse factorials for binomial coefficients

Correctness

Theorem. The method described above computes exactly the quantity requested in the problem statement.

Proof. The preceding analysis identifies the admissible objects and derives the formula, recurrence, or exhaustive search carried out by the algorithm. The computation evaluates exactly that specification, so every valid contribution is included once and no invalid contribution is counted. Therefore the returned value is the required answer. \square

Complexity Analysis

  • Number of inadmissible points: O(sqrt(n)) since a, b <= n^(1/4) ~ 56 for n = 10^7.
  • Actually a^2 <= n means a <= sqrt(n) ~ 3162. The number of Pythagorean triples is O(n^(1/2)).
  • DP: O(m^2) where m is the number of inadmissible points.
  • Binomial coefficients: O(n) precomputation.

Answer

299742733\boxed{299742733}

Code

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

C++ project_euler/problem_408/solution.cpp
#include <bits/stdc++.h>
using namespace std;

/*
 * Project Euler Problem 408: Admissible Paths Through a Grid
 *
 * Inadmissible point (x,y): x, y, x+y all positive perfect squares.
 * This means x=a^2, y=b^2, a^2+b^2=c^2 (Pythagorean triple).
 *
 * P(n) = admissible paths from (0,0) to (n,n).
 * Use inclusion-exclusion DP over inadmissible points.
 */

const long long MOD = 1000000007;
const int MAXF = 20000002;

long long fac[MAXF], inv_fac[MAXF];

long long power(long long base, long long exp, long long mod) {
    long long result = 1;
    base %= mod;
    while (exp > 0) {
        if (exp & 1) result = result * base % mod;
        base = base * base % mod;
        exp >>= 1;
    }
    return result;
}

void precompute_factorials(int n) {
    fac[0] = 1;
    for (int i = 1; i <= n; i++)
        fac[i] = fac[i-1] * i % MOD;
    inv_fac[n] = power(fac[n], MOD - 2, MOD);
    for (int i = n - 1; i >= 0; i--)
        inv_fac[i] = inv_fac[i+1] * (i+1) % MOD;
}

long long C(int n, int k) {
    if (k < 0 || k > n) return 0;
    return fac[n] % MOD * inv_fac[k] % MOD * inv_fac[n-k] % MOD;
}

int main() {
    ios_base::sync_with_stdio(false);

    const int N = 10000000;
    precompute_factorials(2 * N);

    // Generate all Pythagorean triples (a,b,c) with a^2 <= N and b^2 <= N
    // Inadmissible points: (a^2, b^2) where a^2 + b^2 = c^2
    // Generate all primitive triples using Euclid's formula and scale
    int sq = (int)sqrt((double)N);

    set<pair<int,int>> pts_set;

    // Euclid's formula: a = m^2 - n^2, b = 2mn, c = m^2 + n^2
    // where m > n > 0, gcd(m,n) = 1, m-n odd
    for (int m = 1; (long long)m * m <= 2LL * N; m++) {
        for (int n = 1; n < m; n++) {
            if ((m - n) % 2 == 0) continue;
            if (__gcd(m, n) != 1) continue;

            long long a0 = (long long)m * m - (long long)n * n;
            long long b0 = 2LL * m * n;

            // Scale by k: triple is (k*a0, k*b0, k*c0)
            // Need (k*a0)^2 <= N and (k*b0)^2 <= N
            for (long long k = 1; ; k++) {
                long long a = k * a0, b = k * b0;
                if (a * a > N || b * b > N) {
                    if (a * a > N && b * b > N) break;
                    // Only one exceeds, but we also try swapped
                    if (b * b <= N && a * a <= N) {
                        // both ok
                    }
                    // Check both orderings
                    if (a * a <= N && b * b <= N)
                        pts_set.insert({(int)(a*a), (int)(b*b)});
                    if (b * b <= N && a * a <= N)
                        pts_set.insert({(int)(b*b), (int)(a*a)});
                    break;
                }
                pts_set.insert({(int)(a*a), (int)(b*b)});
                pts_set.insert({(int)(b*b), (int)(a*a)});
            }
        }
    }

    // Convert to sorted vector
    vector<pair<int,int>> pts(pts_set.begin(), pts_set.end());
    // Sort by x+y, then by x
    sort(pts.begin(), pts.end(), [](const pair<int,int>& a, const pair<int,int>& b) {
        int sa = a.first + a.second, sb = b.first + b.second;
        if (sa != sb) return sa < sb;
        return a.first < b.first;
    });

    int m = pts.size();

    // DP: f[i] = # paths from (0,0) to pts[i] avoiding all other inadmissible points
    vector<long long> f(m);
    for (int i = 0; i < m; i++) {
        int xi = pts[i].first, yi = pts[i].second;
        f[i] = C(xi + yi, xi);
        for (int j = 0; j < i; j++) {
            int xj = pts[j].first, yj = pts[j].second;
            if (xj <= xi && yj <= yi) {
                f[i] = (f[i] - f[j] % MOD * C(xi - xj + yi - yj, xi - xj) % MOD + MOD) % MOD;
            }
        }
    }

    // P(N) = C(2N, N) - sum f[i] * C(2N - xi - yi, N - xi)
    long long ans = C(2 * N, N);
    for (int i = 0; i < m; i++) {
        int xi = pts[i].first, yi = pts[i].second;
        ans = (ans - f[i] % MOD * C(2 * N - xi - yi, N - xi) % MOD + MOD) % MOD;
    }

    printf("%lld\n", ans);
    return 0;
}