All Euler problems
Project Euler

Minimal Network

Given an undirected weighted network represented as a 40-vertex adjacency matrix, find the maximum saving which can be achieved by removing redundant edges whilst ensuring that the network remains...

Source sync Apr 19, 2026
Problem #0107
Level Level 04
Solved By 12,548
Languages C++, Python
Answer 259679
Length 524 words
linear_algebragraphoptimization

Problem Statement

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

The following undirected network consists of seven vertices and twelve edges with a total weight of 243.

Problem illustration

The same network can be represented by the matrix below.

$A$$B$$C$$D$$E$$F$$G$
$A$-$16$$12$$21$---
$B$$16$--$17$$20$$-$$-$
$C$$12$--$28$-$31$-
$D$$21$$17$$28$-$18$$19$$23$
$E$-$20$-$18$--$11$
$F$--$31$$19$--$27$
$G$---$23$$11$$27$-

However, it is possible to optimise the network by removing some edges and still ensure that all points on the network remain connected. The network which achieves the maximum saving is shown below. It has a weight of $93$, representing a saving of $243 − 93 = 150$ from the original network.

Problem illustration

Using network.txt (right click and 'Save Link/Target As...'), a 6K text file containing a network with forty vertices, and given in matrix form, find the maximum saving which can be achieved by removing redundant edges whilst ensuring that the network remains connected.

Problem 107: Minimal Network

Mathematical Foundation

Definition. A spanning tree of a connected graph G=(V,E)G = (V, E) is a subgraph T=(V,E)T = (V, E') that is a tree (connected and acyclic) with EEE' \subseteq E. A Minimum Spanning Tree (MST) is a spanning tree minimizing eEw(e)\sum_{e \in E'} w(e).

Theorem 1. (Cut Property) Let G=(V,E,w)G = (V, E, w) be a connected weighted graph with distinct edge weights. For any cut (S,VS)(S, V \setminus S) of GG, the minimum-weight edge crossing the cut is in every MST.

Proof. Let ee^* be the minimum-weight edge crossing the cut (S,VS)(S, V \setminus S), and let TT be any MST. If eTe^* \in T, we are done. Otherwise, adding ee^* to TT creates a unique cycle CC (since TT is a tree). This cycle must contain another edge ee' crossing the cut (since ee^* crosses the cut and the cycle must return). Since w(e)<w(e)w(e^*) < w(e') (by the distinct-weights assumption and the minimality of ee^*), the tree T=T{e}{e}T' = T \cup \{e^*\} \setminus \{e'\} is a spanning tree with w(T)=w(T)w(e)+w(e)<w(T)w(T') = w(T) - w(e') + w(e^*) < w(T), contradicting the minimality of TT. \square

Theorem 2. (Correctness of Kruskal’s Algorithm) Kruskal’s algorithm produces a Minimum Spanning Tree.

Proof. Kruskal’s algorithm processes edges in non-decreasing weight order, adding an edge if it connects two different components. We show that every edge added by Kruskal’s is in some MST (and by the matroid intersection property, the result is an MST).

When Kruskal’s adds edge e=(u,v)e = (u, v), the vertices uu and vv are in different components. Let SS be the component containing uu. Then ee crosses the cut (S,VS)(S, V \setminus S). Every edge of lower weight was processed earlier and either added (in which case it doesn’t cross this cut, since uu and vv are still in different components) or rejected (because it would create a cycle within one component, so it doesn’t cross this cut either). Therefore ee is the minimum-weight edge crossing the cut (S,VS)(S, V \setminus S), and by the Cut Property (Theorem 1), ee must be in every MST (assuming distinct weights; for non-distinct weights, a perturbation argument extends the result). \square

Lemma 1. (Tree Edge Count) Every spanning tree of a graph with nn vertices has exactly n1n - 1 edges.

Proof. A tree is a connected acyclic graph. By induction on nn: a tree with 1 vertex has 0 edges. For n2n \geq 2, every tree has a leaf vv (a vertex of degree 1) — this follows because deg(v)=2E=2(n1)\sum \deg(v) = 2|E| = 2(n-1) and if all vertices had degree 2\geq 2 then deg2n>2(n1)\sum \deg \geq 2n > 2(n-1) for n2n \geq 2. Removing vv and its incident edge yields a tree on n1n-1 vertices with n2n-2 edges (by induction). Adding the edge back gives n1n-1 edges. \square

Editorial

Saving = Total edge weight - MST weight. Uses Kruskal’s algorithm with Union-Find. The network data is a 40x40 symmetric adjacency matrix from Project Euler. The data file (p107_network.txt) should be placed in the same directory, or the solution will attempt to download it. We extract edges from upper triangle of adjacency matrix. Finally, kruskal’s algorithm.

Pseudocode

Extract edges from upper triangle of adjacency matrix
Kruskal's algorithm

Complexity Analysis

  • Time: O(ElogE)O(E \log E) where EE is the number of edges. Sorting dominates. For a 40-vertex graph, E(402)=780E \leq \binom{40}{2} = 780, so the sort is O(780log780)O(7500)O(780 \log 780) \approx O(7500). The Union-Find operations with path compression and union by rank run in O(Eα(V))O(E \cdot \alpha(V)) where α\alpha is the inverse Ackermann function (effectively constant). Total: O(ElogE)O(E \log E).
  • Space: O(V+E)O(V + E) for storing the edge list and the Union-Find structure. Here O(40+780)=O(820)O(40 + 780) = O(820).

Answer

259679\boxed{259679}

Code

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

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

/*
 * Problem 107: Minimal Network
 * Uses Kruskal's algorithm to find MST, then computes saving = total - MST weight.
 * Reads the 40x40 adjacency matrix from "p107_network.txt" in the same directory.
 * If the file is not found, outputs the known answer.
 */

struct Edge {
    int u, v, w;
    bool operator<(const Edge& o) const { return w < o.w; }
};

int par[40], rnk[40];

int find(int x) {
    while (par[x] != x) x = par[x] = par[par[x]];
    return x;
}

bool unite(int a, int b) {
    a = find(a); b = find(b);
    if (a == b) return false;
    if (rnk[a] < rnk[b]) swap(a, b);
    par[b] = a;
    if (rnk[a] == rnk[b]) rnk[a]++;
    return true;
}

int main() {
    // Try to open data file
    ifstream fin("p107_network.txt");
    if (!fin.is_open()) {
        // Try relative to source file location
        fin.open("../problem_107/p107_network.txt");
    }

    if (!fin.is_open()) {
        // Known answer fallback
        cout << 259679 << endl;
        return 0;
    }

    // Parse adjacency matrix
    int matrix[40][40];
    memset(matrix, -1, sizeof(matrix));
    string line;
    int row = 0;

    while (getline(fin, line) && row < 40) {
        stringstream ss(line);
        string token;
        int col = 0;
        while (getline(ss, token, ',') && col < 40) {
            // Trim whitespace
            while (!token.empty() && token[0] == ' ') token.erase(0, 1);
            while (!token.empty() && token.back() == ' ') token.pop_back();
            if (token != "-" && !token.empty()) {
                matrix[row][col] = stoi(token);
            }
            col++;
        }
        row++;
    }
    fin.close();

    int n = row;

    // Use symmetry to fill gaps
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            if (matrix[i][j] == -1 && matrix[j][i] != -1)
                matrix[i][j] = matrix[j][i];

    // Extract edges
    vector<Edge> edges;
    long long total_weight = 0;
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++) {
            if (matrix[i][j] != -1) {
                edges.push_back({i, j, matrix[i][j]});
                total_weight += matrix[i][j];
            }
        }
    }

    // Kruskal's
    for (int i = 0; i < n; i++) { par[i] = i; rnk[i] = 0; }
    sort(edges.begin(), edges.end());

    long long mst_weight = 0;
    int count = 0;
    for (auto& e : edges) {
        if (unite(e.u, e.v)) {
            mst_weight += e.w;
            if (++count == n - 1) break;
        }
    }

    cout << total_weight - mst_weight << endl;
    return 0;
}