All Euler problems
Project Euler

Gnomon Numbering

For integers m and n with 0 <= n < m, define L(m,n) as the L-shaped region (gnomon) formed by an m x m grid with the top-right n x n portion removed. Let LC(m,n) count the number of ways to fill th...

Source sync Apr 19, 2026
Problem #0412
Level Level 21
Solved By 596
Languages C++, Python
Answer 38788800
Length 451 words
modular_arithmeticalgebrabrute_force

Problem Statement

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

For integers (), let be an grid with the top-right grid removed.

For example, looks like this:

0412_table53.png

We want to number each cell of with consecutive integers such that the number in every cell is smaller than the number below it and to the left of it.

For example, here are two valid numberings of :

0412_tablenums.png

Let be the number of valid numberings of .
It can be verified that , , and .

Find .

Problem 412: Gnomon Numbering

Mathematical Foundation

Theorem 1 (Hook Length Formula, Frame—Robinson—Thrall). Let λ\lambda be a partition (Young diagram) with NN cells. The number of standard Young tableaux of shape λ\lambda is

fλ=N!(i,j)λh(i,j)f^\lambda = \frac{N!}{\displaystyle\prod_{(i,j) \in \lambda} h(i,j)}

where h(i,j)=arm(i,j)+leg(i,j)+1h(i,j) = \operatorname{arm}(i,j) + \operatorname{leg}(i,j) + 1 is the hook length at cell (i,j)(i,j).

Proof. This is a classical result in algebraic combinatorics. See Frame, Robinson, and Thrall (1954). The proof proceeds by establishing a bijection between standard Young tableaux and certain hook-removal sequences, or alternatively via the theory of symmetric functions and the RSK correspondence. \square

Lemma 1 (Gnomon as Young Diagram). The gnomon L(m,n)L(m,n) corresponds to the partition

λ=(m,m,,ma,  a,a,,an)\lambda = (\underbrace{m, m, \dots, m}_{a},\; \underbrace{a, a, \dots, a}_{n})

where a=mna = m - n. A valid filling of L(m,n)L(m,n) is precisely a standard Young tableau of shape λ\lambda.

Proof. The gnomon L(m,n)L(m,n) consists of an m×mm \times m grid minus the top-right n×nn \times n block. Reading row lengths from top to bottom: the first a=mna = m-n rows have length mm, and the remaining nn rows have length a=mna = m-n (since columns a+1a+1 through mm are removed for these rows). This is exactly the partition λ\lambda. The filling constraint---each cell is smaller than the cell below and to its right---is the definition of a standard Young tableau. \square

Lemma 2 (Hook Length Decomposition). With a=mna = m - n, the cells of λ\lambda decompose into three rectangular regions with hook lengths:

RegionRowsColumnsh(i,j)h(i,j)
1 (top-left)0i<a0 \le i < a0j<a0 \le j < a2mij12m - i - j - 1
2 (top-right)0i<a0 \le i < aaj<ma \le j < mm+aij1m + a - i - j - 1
3 (bottom-left)ai<ma \le i < m0j<a0 \le j < aa+mij1a + m - i - j - 1

Proof. For cell (i,j)(i,j) in Region 1 (i<ai < a, j<aj < a): the arm length is mj1m - j - 1 (cells to the right in a row of length mm), the leg length is mi1m - i - 1 (cells below in a column of full height mm), giving h(i,j)=(mj1)+(mi1)+1=2mij1h(i,j) = (m-j-1) + (m-i-1) + 1 = 2m - i - j - 1.

For Region 2 (i<ai < a, jaj \ge a): the arm length is mj1m - j - 1, the leg length is ai1a - i - 1 (only the first aa rows extend this far right), so h(i,j)=(mj1)+(ai1)+1=m+aij1h(i,j) = (m-j-1) + (a-i-1) + 1 = m + a - i - j - 1.

For Region 3 (iai \ge a, j<aj < a): the arm length is aj1a - j - 1 (rows iai \ge a have length aa), the leg length is mi1m - i - 1, so h(i,j)=(aj1)+(mi1)+1=a+mij1h(i,j) = (a-j-1) + (m-i-1) + 1 = a + m - i - j - 1. \square

Theorem 2 (Row-wise Factorization). The product of all hook lengths decomposes as

(i,j)λh(i,j)=i=0a1(2mi1)!(m+ni1)!i=0a1(mi1)!(ai1)!i=am1(a+mi1)!(mi1)!\prod_{(i,j) \in \lambda} h(i,j) = \prod_{i=0}^{a-1} \frac{(2m-i-1)!}{(m+n-i-1)!} \cdot \prod_{i=0}^{a-1} \frac{(m-i-1)!}{(a-i-1)!} \cdot \prod_{i=a}^{m-1} \frac{(a+m-i-1)!}{(m-i-1)!}

Proof. For each row ii in Region 1, the product over j=0,,a1j = 0, \dots, a-1 of (2mij1)(2m-i-j-1) is a falling factorial:

j=0a1(2mij1)=(2mi1)!(2mia1)!=(2mi1)!(m+ni1)!.\prod_{j=0}^{a-1}(2m-i-j-1) = \frac{(2m-i-1)!}{(2m-i-a-1)!} = \frac{(2m-i-1)!}{(m+n-i-1)!}.

The other two regions follow analogously. \square

Editorial

Compute LC(m, n) = number of Standard Young Tableaux of the L-shaped (gnomon) partition lambda = (m^{m-n}, (m-n)^n), modulo a prime p. Uses the hook length formula: f^lambda = N! / prod h(i,j) where hook lengths decompose into three rectangular regions. We precompute factorial and inverse factorial mod p. Finally, compute product of hook lengths.

Pseudocode

Precompute factorial and inverse factorial mod p
Compute product of hook lengths

Complexity Analysis

  • Time: O(N)O(N) for factorial precomputation where N=m2n2N = m^2 - n^2, plus O(m)O(m) for the three row loops. Total: O(m2)O(m^2).
  • Space: O(N)=O(m2)O(N) = O(m^2) for factorial and inverse factorial tables.

Answer

38788800\boxed{38788800}

Code

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

C++ project_euler/problem_412/solution.cpp
/*
 * Project Euler Problem 412: Gnomon Numbering
 *
 * Compute LC(10000, 5000) mod 76543217 using the hook length formula
 * for Standard Young Tableaux of the L-shaped partition.
 *
 * Partition: lambda = (m^{m-n}, (m-n)^n)
 * Hook length formula: f^lambda = N! / prod h(i,j)
 *
 * The hooks decompose into three rectangular regions, each row
 * contributing a ratio of consecutive factorials.
 *
 * Answer: 38788800
 *
 * Compile: g++ -O2 -o solution solution.cpp
 */

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const ll MOD = 76543217;

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

ll solve(int m, int n, ll p) {
    int a = m - n;
    ll N = (ll)m * m - (ll)n * n;

    ll max_val = max(N, (ll)(2 * m)) + 10;

    // Precompute factorials mod p
    vector<ll> fact(max_val + 1), inv_fact(max_val + 1);
    fact[0] = 1;
    for (ll i = 1; i <= max_val; i++)
        fact[i] = fact[i - 1] * i % p;

    inv_fact[max_val] = power(fact[max_val], p - 2, p);
    for (ll i = max_val - 1; i >= 0; i--)
        inv_fact[i] = inv_fact[i + 1] * (i + 1) % p;

    ll numerator = fact[N];
    ll denom = 1;

    // Region 1: rows [0, a), cols [0, a)
    // Row i hook product = fact[2m-i-1] * inv_fact[2m-i-a-1]
    for (int i = 0; i < a; i++) {
        int top = 2 * m - i - 1;
        int bot = 2 * m - i - a - 1;
        denom = denom * fact[top] % p * inv_fact[bot] % p;
    }

    // Region 2: rows [0, a), cols [a, m)
    // Row i hook product = fact[m-i-1] * inv_fact[a-i-1]
    for (int i = 0; i < a; i++) {
        int top = m - i - 1;
        int bot = a - i - 1;
        denom = denom * fact[top] % p * inv_fact[bot] % p;
    }

    // Region 3: rows [a, m), cols [0, a)
    // Row i hook product = fact[a+m-i-1] * inv_fact[m-i-1]
    for (int i = a; i < m; i++) {
        int top = a + m - i - 1;
        int bot = m - i - 1;
        denom = denom * fact[top] % p * inv_fact[bot] % p;
    }

    return numerator % p * power(denom, p - 2, p) % p;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    ll p = MOD;

    // Verification
    cout << "LC(3, 0) = " << solve(3, 0, p) << " (expected 42)" << endl;
    cout << "LC(5, 3) = " << solve(5, 3, p) << " (expected 250250)" << endl;
    cout << "LC(10, 5) = " << solve(10, 5, p) << " (expected 61251715)" << endl;

    // Main problem
    cout << endl;
    cout << "LC(10000, 5000) mod " << p << " = " << solve(10000, 5000, p) << endl;

    return 0;
}