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...
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$.

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 , , modulus . The signed values range from to .
Lemma. The LCG is full-period since (797807 is odd) and is divisible by all prime factors of (namely 2), and 4 divides .
Prefix Sum Technique
Theorem. For each row , define the prefix sum . Then the sum of entries from column to column in row is:
This allows each row-slice sum to be computed in .
Sub-triangle Sum Recurrence
For a sub-triangle with apex :
By maintaining a running sum as increases, each new depth adds one row-slice lookup.
Complexity of Enumeration
The total number of (apex, depth) combinations is:
For : approximately operations.
Why Negative Sums Exist
Since with mean approximately , many sub-triangles have negative sums. The minimum-sum sub-triangle exploits regions of concentrated negative values.
Verification
- Total number of entries: .
- 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 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 . Starting from height zero, it keeps a running subtotal and repeatedly adds the next row slice
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: for .
- Space: for the array and prefix sums.
Answer
Code
Each problem page includes the exact C++ and Python source files from the local archive.
#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;
}
"""
Problem 150: Minimum-sum Sub-triangle in Triangular Array
Uses LCG to generate entries, prefix sums for O(1) row-slice queries,
and exhaustive enumeration of all sub-triangles.
"""
def solve(N: int = 1000):
MOD = 1 << 20
HALF = 1 << 19
# Generate triangular array
tri = []
t = 0
for r in range(N):
row = []
for c in range(r + 1):
t = (615949 * t + 797807) % MOD
row.append(t - HALF)
tri.append(row)
# Prefix sums per row
prefix = []
for r in range(N):
p = [0] * (r + 2)
for c in range(r + 1):
p[c + 1] = p[c] + tri[r][c]
prefix.append(p)
# Find minimum sub-triangle sum
ans = float('inf')
for r in range(N):
for c in range(r + 1):
tri_sum = 0
for h in range(N - r):
tri_sum += prefix[r + h][c + h + 1] - prefix[r + h][c]
if tri_sum < ans:
ans = tri_sum
return ans
def solve_brute_small(N: int = 10):
"""Brute force for small N to verify."""
MOD = 1 << 20
HALF = 1 << 19
tri = []
t = 0
for r in range(N):
row = []
for c in range(r + 1):
t = (615949 * t + 797807) % MOD
row.append(t - HALF)
tri.append(row)
ans = float('inf')
for r in range(N):
for c in range(r + 1):
for h in range(N - r):
s = sum(tri[r + i][c + j] for i in range(h + 1) for j in range(i + 1))
# This is wrong - should be c to c+i
pass
# Use prefix sum version for brute force too
prefix = []
for r in range(N):
p = [0] * (r + 2)
for c in range(r + 1):
p[c + 1] = p[c] + tri[r][c]
prefix.append(p)
for r in range(N):
for c in range(r + 1):
tri_sum = 0
for h in range(N - r):
tri_sum += prefix[r + h][c + h + 1] - prefix[r + h][c]
if tri_sum < ans:
ans = tri_sum
return ans
# Verify on small case
small_ans = solve_brute_small(10)
small_ans2 = solve(10)
assert small_ans == small_ans2, f"Small case mismatch: {small_ans} vs {small_ans2}"
# Main solve
answer = solve(1000)
assert answer == -271248680, f"Expected -271248680, got {answer}"
print(answer)