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 May 21, 2026
Problem #0107
Level Level 04
Solved By 12,548
Languages C++, Python
Answer 259679
Length 559 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 E′⊆EE' \subseteq E. A Minimum Spanning Tree (MST) is a spanning tree minimizing ∑e∈E′w(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,V∖S)(S, V \setminus S) of GG, the minimum-weight edge crossing the cut is in every MST.

Proof. Let e∗e^* be the minimum-weight edge crossing the cut (S,V∖S)(S, V \setminus S), and let TT be any MST. If e∗∈Te^* \in T, we are done. Otherwise, adding e∗e^* to TT creates a unique cycle CC (since TT is a tree). This cycle must contain another edge e′e' crossing the cut (since e∗e^* 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 e∗e^*), 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,V∖S)(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,V∖S)(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 n−1n - 1 edges.

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

Editorial

This is a minimum-spanning-tree computation. The adjacency matrix describes an undirected graph, so each usable entry in the upper triangle corresponds to one edge, and summing those entries gives the total weight of the original network.

After the edge list is built, the implementation sorts the edges by weight and runs Kruskal’s algorithm with a disjoint-set structure. Every time the next lightest edge joins two previously separate components, that edge is accepted into the spanning tree. The saving is then the original total weight minus the weight of the resulting MST.

Pseudocode

Read the adjacency matrix and turn every finite upper-triangular entry into an undirected edge.
Add all of those edge weights to obtain the weight of the full network.
Sort the edges from lightest to heaviest.
Initialize one disjoint-set component for each vertex.

Walk through the sorted edges:
    If an edge connects two different components, merge those components and add the edge weight to the spanning-tree total.
    Stop once the spanning tree has n - 1 edges.

Return the full-network weight minus the spanning-tree weight.

Complexity Analysis

  • Time: O(Elog⁡E)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(780log⁡780)≈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(Elog⁡E)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;
}