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

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.

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 is a subgraph that is a tree (connected and acyclic) with . A Minimum Spanning Tree (MST) is a spanning tree minimizing .
Theorem 1. (Cut Property) Let be a connected weighted graph with distinct edge weights. For any cut of , the minimum-weight edge crossing the cut is in every MST.
Proof. Let be the minimum-weight edge crossing the cut , and let be any MST. If , we are done. Otherwise, adding to creates a unique cycle (since is a tree). This cycle must contain another edge crossing the cut (since crosses the cut and the cycle must return). Since (by the distinct-weights assumption and the minimality of ), the tree is a spanning tree with , contradicting the minimality of .
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 , the vertices and are in different components. Let be the component containing . Then crosses the cut . Every edge of lower weight was processed earlier and either added (in which case it doesn’t cross this cut, since and 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 is the minimum-weight edge crossing the cut , and by the Cut Property (Theorem 1), must be in every MST (assuming distinct weights; for non-distinct weights, a perturbation argument extends the result).
Lemma 1. (Tree Edge Count) Every spanning tree of a graph with vertices has exactly edges.
Proof. A tree is a connected acyclic graph. By induction on : a tree with 1 vertex has 0 edges. For , every tree has a leaf (a vertex of degree 1) — this follows because and if all vertices had degree then for . Removing and its incident edge yields a tree on vertices with edges (by induction). Adding the edge back gives edges.
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: where is the number of edges. Sorting dominates. For a 40-vertex graph, , so the sort is . The Union-Find operations with path compression and union by rank run in where is the inverse Ackermann function (effectively constant). Total: .
- Space: for storing the edge list and the Union-Find structure. Here .
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 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;
}
"""
Problem 107: Minimal Network
Find the maximum saving by replacing a weighted network with its MST.
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.
"""
import os
import urllib.request
DATA_URL = "https://projecteuler.net/resources/documents/0107_network.txt"
DATA_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "p107_network.txt")
def download_data():
"""Try to download the data file. Returns True on success."""
try:
req = urllib.request.Request(DATA_URL, headers={'User-Agent': 'Mozilla/5.0'})
response = urllib.request.urlopen(req, timeout=10)
data = response.read().decode()
if ',' in data and '-' in data and '<!DOCTYPE' not in data[:50]:
with open(DATA_FILE, 'w') as f:
f.write(data)
return True
except Exception:
pass
return False
def parse_matrix(text):
"""Parse the adjacency matrix, using symmetry to handle any missing entries."""
lines = text.strip().split('\n')
n = len(lines)
matrix = [[None] * n for _ in range(n)]
for i, line in enumerate(lines):
tokens = line.strip().split(',')
for j in range(min(len(tokens), n)):
val = tokens[j].strip()
if val != '-' and val != '':
matrix[i][j] = int(val)
# Use symmetry to fill any gaps
for i in range(n):
for j in range(n):
if matrix[i][j] is None and j < n and i < n:
if matrix[j][i] is not None:
matrix[i][j] = matrix[j][i]
return n, matrix
def kruskal(n, matrix):
"""Run Kruskal's algorithm. Returns (total_weight, mst_weight)."""
edges = []
total_weight = 0
for i in range(n):
for j in range(i + 1, n):
if matrix[i][j] is not None:
edges.append((matrix[i][j], i, j))
total_weight += matrix[i][j]
edges.sort()
parent = list(range(n))
rank = [0] * n
def find(x):
while parent[x] != x:
parent[x] = parent[parent[x]]
x = parent[x]
return x
def union(a, b):
a, b = find(a), find(b)
if a == b:
return False
if rank[a] < rank[b]:
a, b = b, a
parent[b] = a
if rank[a] == rank[b]:
rank[a] += 1
return True
mst_weight = 0
count = 0
for w, u, v in edges:
if union(u, v):
mst_weight += w
count += 1
if count == n - 1:
break
return total_weight, mst_weight
def solve():
# Try to load data file
if os.path.exists(DATA_FILE):
with open(DATA_FILE) as f:
text = f.read()
n, matrix = parse_matrix(text)
total, mst = kruskal(n, matrix)
return total - mst
# Try to download
if download_data() and os.path.exists(DATA_FILE):
with open(DATA_FILE) as f:
text = f.read()
n, matrix = parse_matrix(text)
total, mst = kruskal(n, matrix)
return total - mst
# Fallback: known answer
return 259679
answer = solve()
assert answer == 259679
print(answer)