All Euler problems
Project Euler

Criss Cross

A 4 x 4 grid is filled with digits d (0 <= d <= 9), and we require that every row, every column, and both main diagonals all share the same sum S. How many such grids exist?

Source sync May 21, 2026
Problem #0166
Level Level 07
Solved By 4,733
Languages C++, Python
Answer 7130034
Length 486 words
optimizationlinear_algebrabrute_force

Problem Statement

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

A \(4 \times 4\) grid is filled with digits \(d\), \(0 \le d \le 9\). \[ \begin {matrix} 6 & 3 & 3 & 0\\ 5 & 0 & 4 & 3\\ 0 & 7 & 1 & 4\\ 1 & 2 & 4 & 5 \end {matrix} \] the sum of each row and each column has the value \(12\). Moreover the sum of each diagonal is also \(12\).

In how many ways can you fill a \(4 \times 4\) grid with the digits \(d\), \(0 \le d \le 9\) so that each row, each column, and both diagonals have the same sum?

Problem 166: Criss Cross

Mathematical Development

Definition. A semi-magic square of order 4 over digits is a 4×44 \times 4 matrix (aij)(a_{ij}) with aij∈{0,1,…,9}a_{ij} \in \{0, 1, \ldots, 9\} such that all row sums, column sums, and both main diagonal sums equal a common value SS.

Theorem 1 (Constraint rank). The 10 linear constraints (4 rows + 4 columns + 2 diagonals summing to SS) have rank 7 over Q\mathbb{Q}, leaving 9 free variables among the 16 entries.

Proof. Write the grid as:

(abcdefghijklmnop)\begin{pmatrix} a & b & c & d \\ e & f & g & h \\ i & j & k & l \\ m & n & o & p \end{pmatrix}

The 4 row-sum equations give: a+b+c+d=e+f+g+h=i+j+k+l=m+n+o+p=Sa+b+c+d = e+f+g+h = i+j+k+l = m+n+o+p = S. The 4 column-sum equations give: a+e+i+m=b+f+j+n=c+g+k+o=d+h+l+p=Sa+e+i+m = b+f+j+n = c+g+k+o = d+h+l+p = S. The 2 diagonal equations: a+f+k+p=d+g+j+m=Sa+f+k+p = d+g+j+m = S.

Summing all 4 row equations gives the same total (4S4S) as summing all 4 column equations, so these 8 equations are not independent. The row equations determine 3 independent constraints (given SS). The column equations add 3 more (since one column sum is implied by the other three plus the total). The diagonal equations add 1 more (since the sum of both diagonals relates to the row/column sums). Total independent constraints: 3+3+1=73 + 3 + 1 = 7 (plus SS itself as a parameter). □\square

Theorem 2 (Fourth row determination). Given rows 1—3 each summing to SS, the fourth row is uniquely determined by the column-sum constraints:

m=S−a−e−i,n=S−b−f−j,o=S−c−g−k,p=S−d−h−lm = S - a - e - i, \quad n = S - b - f - j, \quad o = S - c - g - k, \quad p = S - d - h - l

Moreover, m+n+o+p=Sm + n + o + p = S is automatically satisfied.

Proof. Each fourth-row entry is forced by the corresponding column sum equaling SS. For the row-sum check: m+n+o+p=4S−(a+b+c+d)−(e+f+g+h)−(i+j+k+l)=4S−3S=Sm+n+o+p = 4S - (a+b+c+d) - (e+f+g+h) - (i+j+k+l) = 4S - 3S = S. □\square

Lemma 1. The magic sum SS ranges from 0 to 36, since each row of four digits in {0,…,9}\{0,\ldots,9\} has sum between 0 and 36.

Proof. Minimum: all zeros, sum = 0. Maximum: all nines, sum = 36. □\square

Lemma 2 (Diagonal constraints as filter). After determining the fourth row, the two diagonal conditions a+f+k+p=Sa + f + k + p = S and d+g+j+m=Sd + g + j + m = S serve as a filter, rejecting configurations that fail either constraint.

Proof. These are the only constraints not yet enforced by the row-sum and column-sum conditions. □\square

Editorial

The code uses meet-in-the-middle around the common sum SS. For a fixed SS, every row must be a 4-tuple of digits summing to SS, so the first step is to generate that row catalog once. Instead of trying all four rows together, the program groups the top two rows and the bottom two rows separately.

For the top half, it records the four partial column sums and the two partial diagonal sums that those rows contribute. For the bottom half, it computes the complementary values needed so that the full columns and diagonals land exactly on SS. A hash map from the top-half signature to its multiplicity then makes the final count a lookup problem: every bottom-half configuration asks how many matching top halves can complete it. This turns a much heavier brute-force search into a manageable join over summarized states.

Pseudocode

Initialize the total answer to zero.

For each possible common sum $S$ from 0 to 36:
    Generate every row of four digits whose entries add up to $S$.

    Enumerate all ordered pairs of top rows.
    For each pair, store in a map:
        the four column sums contributed so far,
        the two diagonal partial sums,
        and how many times this signature occurs.

    Enumerate all ordered pairs of bottom rows.
    For each pair, compute the complementary signature needed from the top half
    so that every column and both diagonals finish with total sum $S$.
    Add the number of matching top-half signatures from the map.

Return the accumulated total.

Complexity Analysis

  • Time: Let T(S)T(S) = number of 4-tuples of digits summing to SS. Naively O(∑ST(S)3)O\bigl(\sum_S T(S)^3\bigr). Since ∑ST(S)=104\sum_S T(S) = 10^4 and T(S)≤220T(S) \leq 220 (for S=18S = 18), the worst case is O(37⋅2203)≈3.9×108O(37 \cdot 220^3) \approx 3.9 \times 10^8, manageable with pruning. With meet-in-the-middle, this drops to O(∑ST(S)2⋅log⁡T(S))O\bigl(\sum_S T(S)^2 \cdot \log T(S)\bigr).
  • Space: O(max⁡ST(S)2)O\bigl(\max_S T(S)^2\bigr) for meet-in-the-middle hash tables, or O(T(S))O(T(S)) for the tuple lists.

Answer

7130034\boxed{7130034}

Code

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

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

int main(){
    // Problem 166: Count 4x4 grids of digits 0-9 where all rows, columns,
    // and both diagonals have the same sum S.
    //
    // Grid:
    // a b c d
    // e f g h
    // i j k l
    // m n o p
    //
    // Given rows 1-3, row 4 is determined by column constraints.
    // We check validity and diagonal constraints.
    //
    // Optimization: for each S, enumerate pairs (row1, row2) as "top half"
    // and store in a map keyed by partial column sums and partial diagonal info.
    // Then enumerate (row3) and derive row4, check constraints.
    //
    // Actually, a direct enumeration with pruning is fast enough.

    long long total = 0;

    for(int S = 0; S <= 36; S++){
        // Generate all 4-tuples of digits summing to S
        vector<array<int,4>> tuples;
        for(int x0 = 0; x0 <= 9 && x0 <= S; x0++)
            for(int x1 = 0; x1 <= 9 && x0+x1 <= S; x1++)
                for(int x2 = 0; x2 <= 9 && x0+x1+x2 <= S; x2++){
                    int x3 = S - x0 - x1 - x2;
                    if(x3 >= 0 && x3 <= 9)
                        tuples.push_back({x0,x1,x2,x3});
                }

        int NT = tuples.size();

        // For meet-in-the-middle: enumerate top 2 rows, store by
        // (col0_partial, col1_partial, col2_partial, col3_partial, diag0_partial, diag1_partial)
        // where diag0 = a+f, diag1 = d+g
        // Key: (c0, c1, c2, c3, d0, d1) where each in [0..18]
        // Use a flat map: key = c0*19^5 + c1*19^4 + c2*19^3 + c3*19^2 + d0*19 + d1

        unordered_map<long long, int> topmap;
        topmap.reserve(NT * NT / 2);

        for(int r1 = 0; r1 < NT; r1++){
            auto &t1 = tuples[r1];
            for(int r2 = 0; r2 < NT; r2++){
                auto &t2 = tuples[r2];
                int c0 = t1[0]+t2[0], c1 = t1[1]+t2[1];
                int c2 = t1[2]+t2[2], c3 = t1[3]+t2[3];
                int d0 = t1[0]+t2[1]; // a + f
                int d1 = t1[3]+t2[2]; // d + g
                long long key = (((((long long)c0*19+c1)*19+c2)*19+c3)*19+d0)*19+d1;
                topmap[key]++;
            }
        }

        // Enumerate bottom 2 rows (row3=i,j,k,l and row4=m,n,o,p)
        // Column constraints: c0+i+m=S => i+m = S-c0, etc.
        // Diag constraints: diag0: k+p, need a+f+k+p=S => k+p = S-d0
        //                   diag1: j+m, need d+g+j+m=S => j+m = S-d1
        // So we need:
        //   bottom_c0 = i+m = S - top_c0
        //   bottom_c1 = j+n = S - top_c1
        //   bottom_c2 = k+o = S - top_c2
        //   bottom_c3 = l+p = S - top_c3
        //   bottom_d0 = k+p = S - top_d0
        //   bottom_d1 = j+m = S - top_d1

        for(int r3 = 0; r3 < NT; r3++){
            auto &t3 = tuples[r3];
            for(int r4 = 0; r4 < NT; r4++){
                auto &t4 = tuples[r4];
                int bc0 = t3[0]+t4[0], bc1 = t3[1]+t4[1];
                int bc2 = t3[2]+t4[2], bc3 = t3[3]+t4[3];
                int bd0 = t3[2]+t4[3]; // k + p
                int bd1 = t3[1]+t4[0]; // j + m

                // We need top values: tc0 = S-bc0, etc.
                int tc0 = S-bc0, tc1 = S-bc1, tc2 = S-bc2, tc3 = S-bc3;
                int td0 = S-bd0, td1 = S-bd1;
                if(tc0<0||tc1<0||tc2<0||tc3<0||td0<0||td1<0) continue;
                if(tc0>18||tc1>18||tc2>18||tc3>18||td0>18||td1>18) continue;

                long long key = (((((long long)tc0*19+tc1)*19+tc2)*19+tc3)*19+td0)*19+td1;
                auto it = topmap.find(key);
                if(it != topmap.end())
                    total += it->second;
            }
        }
    }

    cout << total << endl;
    return 0;
}