All Euler problems
Project Euler

Searching a Triangular Array for a Sub-triangle Having the Minimum-sum

In a triangular array with 1000 rows, the entries are generated by a linear congruential generator: t_k = (615949 * t_(k-1) + 797807) mod 2^20, t_0 = 0 s_k = t_k - 2^19 Row r (0-indexed) has r+1 en...

Source sync May 21, 2026
Problem #0150
Level Level 07
Solved By 4,603
Languages C++, Python
Answer -271248680
Length 339 words
geometryoptimizationmodular_arithmetic

Problem Statement

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

In a triangular array of positive and negative integers, we wish to find a sub-triangle such that the sum of the numbers it contains is the smallest possible.

In the example below, it can be easily verified that the marked triangle satisfies this condition having a sum of $−42$.

Problem illustration

We wish to make such a triangular array with one thousand rows, so we generate $500500$ pseudo-random numbers $s_k$ in the range $\pm 2^{19}$, using a type of random number generator (known as a Linear Congruential Generator) as follows:

t := 0;
  for k = 1 up to k = 500500 do
  begin
   t := (615949 * t + 797807) modulo 220; 
   sk := t - 219;
  end;

Thus: $s_1 = 273519$, $s_2 = -153582$, $s_3 = 450909$ etc

\[ \begin{array}{ccccccc} & & & 1 & & & \\ & & 1 & & 1 & & \\ & 1 & & 2 & & 1 & \\ 1 & & 3 & & 3 & & 1 \\ & & & \ldots & & & \\ \end{array} \]

Sub-triangles can start at any element of the array and extend down as far as we like (taking-in the two elements directly below it from the next row, the three elements directly below from the row after that, and so on).

The "sum of a sub-triangle" is defined as the sum of all the elements it contains.

Find the smallest possible sub-triangle sum.

Problem 150: Searching a Triangular Array for a Sub-triangle Having the Minimum-sum

Mathematical Development

Linear Congruential Generator

The LCG uses parameters a=615949a = 615949, c=797807c = 797807, modulus M=220=1048576M = 2^{20} = 1048576. The signed values sk=tk−219s_k = t_k - 2^{19} range from −524288-524288 to 524287524287.

Lemma. The LCG is full-period since gcd⁡(c,M)=1\gcd(c, M) = 1 (797807 is odd) and a−1=615948a - 1 = 615948 is divisible by all prime factors of MM (namely 2), and 4 divides a−1a - 1.

Prefix Sum Technique

Theorem. For each row rr, define the prefix sum P[r][j]=∑k=0j−1s(r,k)P[r][j] = \sum_{k=0}^{j-1} s(r, k). Then the sum of entries from column cc to column c+ic+i in row rr is:

P[r][c+i+1]−P[r][c]P[r][c+i+1] - P[r][c]

This allows each row-slice sum to be computed in O(1)O(1).

Sub-triangle Sum Recurrence

For a sub-triangle with apex (r,c)(r, c):

TriSum(r,c,h)=TriSum(r,c,h−1)+(P[r+h][c+h+1]−P[r+h][c])\text{TriSum}(r, c, h) = \text{TriSum}(r, c, h-1) + (P[r+h][c+h+1] - P[r+h][c])

By maintaining a running sum as hh increases, each new depth adds one O(1)O(1) row-slice lookup.

Complexity of Enumeration

The total number of (apex, depth) combinations is:

∑r=0N−1∑c=0r(N−r)=∑r=0N−1(r+1)(N−r)=N(N+1)(N+2)6\sum_{r=0}^{N-1} \sum_{c=0}^{r} (N - r) = \sum_{r=0}^{N-1} (r+1)(N-r) = \frac{N(N+1)(N+2)}{6}

For N=1000N = 1000: approximately 1.67×1081.67 \times 10^8 operations.

Why Negative Sums Exist

Since sk∈[−219,219−1]s_k \in [-2^{19}, 2^{19} - 1] with mean approximately −0.5-0.5, many sub-triangles have negative sums. The minimum-sum sub-triangle exploits regions of concentrated negative values.

Verification

  • Total number of entries: ∑r=0999(r+1)=500500\sum_{r=0}^{999} (r+1) = 500500.
  • The minimum sub-triangle sum is negative and large in magnitude.

Editorial

The generator produces half a million entries, but the important observation is that a downward-pointing sub-triangle can be grown one row at a time. Once row prefix sums are available, the contribution of the next row segment under a fixed apex is an O(1)O(1) lookup, so extending the triangle by one level costs constant time instead of recomputing the entire sum.

The implementation therefore has two phases. First it builds the triangular array and the prefix sums for every row. Then it tries every possible apex (r,c)(r,c). Starting from height zero, it keeps a running subtotal and repeatedly adds the next row slice

P[r+h][c+h+1]−P[r+h][c].P[r+h][c+h+1]-P[r+h][c].

Each intermediate subtotal is the sum of one specific downward-pointing sub-triangle, so updating the global minimum during this expansion visits every candidate exactly once.

Pseudocode

Generate the triangular array from the linear congruential generator.

For each row, build a prefix-sum array so any contiguous slice in that row can be read in constant time.

Initialize the best answer to positive infinity.

For every apex position $(r,c)$ in the triangle:
    Set the current sub-triangle sum to zero.
    Increase the height one row at a time while the triangle still fits:
        Add the new bottom row segment using the row prefix sums.
        Compare the updated subtotal against the global minimum.

Return the smallest subtotal encountered.

Complexity Analysis

  • Time: O(N3/6)≈1.67×108O(N^3/6) \approx 1.67 \times 10^8 for N=1000N = 1000.
  • Space: O(N2/2)O(N^2/2) for the array and prefix sums.

Answer

−271248680\boxed{-271248680}

Code

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

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

/*
 * Problem 150: Minimum-sum Sub-triangle
 * Generate triangular array via LCG, use prefix sums, enumerate all sub-triangles.
 * Complexity: O(N^3/6) ~ 1.67e8 for N=1000.
 */

int main() {
    const int N = 1000;
    const long long MOD = 1 << 20;

    // Generate triangular array
    vector<vector<long long>> tri(N);
    long long t = 0;
    for (int r = 0; r < N; r++) {
        tri[r].resize(r + 1);
        for (int c = 0; c <= r; c++) {
            t = (615949LL * t + 797807LL) % MOD;
            tri[r][c] = t - (1 << 19);
        }
    }

    // Prefix sums per row
    vector<vector<long long>> prefix(N);
    for (int r = 0; r < N; r++) {
        prefix[r].resize(r + 2, 0);
        for (int c = 0; c <= r; c++) {
            prefix[r][c + 1] = prefix[r][c] + tri[r][c];
        }
    }

    // Find minimum sub-triangle sum
    long long ans = LLONG_MAX;
    for (int r = 0; r < N; r++) {
        for (int c = 0; c <= r; c++) {
            long long triSum = 0;
            for (int h = 0; r + h < N; h++) {
                triSum += prefix[r + h][c + h + 1] - prefix[r + h][c];
                if (triSum < ans) ans = triSum;
            }
        }
    }

    assert(ans == -271248680LL);
    cout << ans << endl;
    return 0;
}