All Euler problems
Project Euler

Path Sum: Three Ways

In the same 80x80 matrix, find the minimal path sum from any cell in the left column to any cell in the right column, where at each step you may move right, up, or down (but not left).

Source sync Apr 19, 2026
Problem #0082
Level Level 03
Solved By 23,962
Languages C++, Python
Answer 260324
Length 561 words
linear_algebradynamic_programmingoptimization

Problem Statement

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

NOTE: This problem is a more challenging version of Problem 81.

The minimal path sum in the by matrix below, by starting in any cell in the left column and finishing in any cell in the right column, and only moving up, down, and right, is indicated in red and bold; the sum is equal to .

Find the minimal path sum from the left column to the right column in matrix.txt (right click and "Save Link/Target As..."), a 31K text file containing an by matrix.

Problem 82: Path Sum: Three Ways

Mathematical Foundation

Let MM be an n×nn \times n matrix with positive integer entries. We seek:

minvalid paths P(i,j)PM[i][j]\min_{\text{valid paths } P} \sum_{(i,j) \in P} M[i][j]

where a valid path starts at any (i,0)(i, 0), ends at any (i,n1)(i', n-1), and uses only moves (i,j)(i,j+1)(i,j) \to (i,j+1), (i,j)(i1,j)(i,j) \to (i-1,j), or (i,j)(i+1,j)(i,j) \to (i+1,j).

Define D[i][j]D[i][j] as the minimal cost to reach cell (i,j)(i,j) from any cell in column 0.

Lemma 1 (No vertical reversal). In any optimal path, the portion within a single column is a monotone vertical segment (purely upward or purely downward). That is, no optimal path goes up and then down (or down and then up) within the same column. Proof. Suppose an optimal path within column jj visits cells at rows r1,r2,,rmr_1, r_2, \ldots, r_m in that order, and there exist indices a<b<ca < b < c such that ra<rb>rcr_a < r_b > r_c (a down-then-up pattern). Then the path visits some cell (rb,j)(r_b, j) and later revisits a row between rar_a and rbr_b. Since all matrix entries are positive, the cost of the detour through the intermediate cells is strictly positive. Removing the detour (going directly from rar_a to rcr_c via the shorter vertical path) yields a cheaper path, contradicting optimality. The argument for up-then-down is symmetric. \square

Theorem 1 (Two-pass correctness). The following column-by-column algorithm correctly computes D[i][n1]D[i][n-1] for all ii:

Initialization: D[i][0]=M[i][0]D[i][0] = M[i][0] for all ii.

For each column j=1,,n1j = 1, \ldots, n-1:

  1. Set base[i]=D[i][j1]+M[i][j]\mathrm{base}[i] = D[i][j-1] + M[i][j] for all ii.
  2. Downward pass: For i=1,,n1i = 1, \ldots, n-1: D[i][j]=min(base[i],  D[i1][j]+M[i][j])D[i][j] = \min(\mathrm{base}[i],\; D[i-1][j] + M[i][j]).
  3. Upward pass: For i=n2,,0i = n-2, \ldots, 0: D[i][j]=min(D[i][j],  D[i+1][j]+M[i][j])D[i][j] = \min(D[i][j],\; D[i+1][j] + M[i][j]).

Proof. By Lemma 1, any optimal path arriving at (i,j)(i,j) either enters directly from (i,j1)(i,j-1), or enters from some (k,j1)(k,j-1) and then moves purely vertically to row ii. If k<ik < i, the path moves downward from row kk to row ii; the downward pass captures this by propagating costs from lower-indexed rows. If k>ik > i, the path moves upward; the upward pass captures this. Since both directions are covered and vertical reversals are suboptimal, the two passes suffice.

More formally, after the downward pass, D[i][j]D[i][j] equals the minimum cost among all paths that either enter (i,j)(i,j) from the left or enter some (k,j)(k,j) with kik \leq i from the left and travel down. After the upward pass, D[i][j]D[i][j] is further minimized over paths entering from below (i.e., from (k,j)(k,j) with k>ik > i traveling up). Together, all cases are covered. \square

Theorem 2. The answer is min0in1D[i][n1]\min_{0 \leq i \leq n-1} D[i][n-1].

Proof. The problem requires the minimum over all valid paths. Since any valid path ends at some cell in column n1n-1, the global minimum is the minimum over all ending rows. \square

Editorial

The key observation is that when we process one column at a time, every valid path enters the new column from the left exactly once and then moves vertically within that column. Because an optimal path never reverses vertical direction inside the same column, the only possibilities are a downward sweep or an upward sweep after entering.

That is why two passes are enough. We first assume every row enters directly from the left, then propagate improvements downward to capture paths that enter higher and move down, and finally propagate improvements upward to capture paths that enter lower and move up. The candidates for each row in the current column are exactly these three forms, so after the two sweeps the column is fully optimized.

Pseudocode

Initialize the current cost for each row using the values in the leftmost column.

For each later column:
    Start a new column cost array by entering every row directly from the left

    Sweep from top to bottom:
        update each row using the possibility of coming from the row above

    Sweep from bottom to top:
        update each row using the possibility of coming from the row below

    Replace the current cost array with this optimized column

Return the smallest value in the final column.

Complexity Analysis

Time: O(n2)O(n^2). There are nn columns, and for each column the downward and upward passes each take O(n)O(n) time, giving O(n)O(n) per column and O(n2)O(n^2) total.

Space: O(n)O(n). Only two arrays of length nn are needed: the current column’s DP values and the base array.

Answer

260324\boxed{260324}

Code

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

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

int main(){
    // Problem 82: Path Sum Three Ways
    // Minimal path sum from left column to right column,
    // moving right, up, or down.

    int n = 80;
    vector<vector<long long>> M(n, vector<long long>(n));

    ifstream fin("p082_matrix.txt");
    if(fin.is_open()){
        for(int i = 0; i < n; i++){
            for(int j = 0; j < n; j++){
                fin >> M[i][j];
                char ch;
                if(j < n-1) fin >> ch;
            }
        }
        fin.close();
    } else {
        n = 5;
        M = {
            {131, 673, 234, 103, 18},
            {201, 96, 342, 965, 150},
            {630, 803, 746, 422, 111},
            {537, 699, 497, 121, 956},
            {805, 732, 524, 37, 331}
        };
        // Expected for 5x5: 994
    }

    // DP column by column
    vector<long long> dp(n);
    for(int i = 0; i < n; i++)
        dp[i] = M[i][0];

    for(int j = 1; j < n; j++){
        vector<long long> ndp(n);

        // Start with cost of coming from the left
        for(int i = 0; i < n; i++)
            ndp[i] = dp[i] + M[i][j];

        // Downward pass
        for(int i = 1; i < n; i++)
            ndp[i] = min(ndp[i], ndp[i-1] + M[i][j]);

        // Upward pass
        for(int i = n-2; i >= 0; i--)
            ndp[i] = min(ndp[i], ndp[i+1] + M[i][j]);

        dp = ndp;
    }

    cout << *min_element(dp.begin(), dp.end()) << endl;
    // Full 80x80 answer: 260324

    return 0;
}