Skip to content

Edge Necessity

You have a graph. You know all shortest distances. Now someone asks: which edges actually matter? Which ones sit on a shortest path? Which ones could vanish without changing any distance?

This is edge forensics. You're not finding paths. You're interrogating the graph's skeleton.


The Problem: ABC051-D Candidates of No Shortest Paths

Critical edge: removing it increases the shortest path distance

Given a weighted undirected graph with \(N\) nodes (\(N \leq 100\)) and \(M\) edges, count the number of edges that do not lie on any shortest path between any pair of vertices.


Step 1: Compute All-Pairs Shortest Paths

\(N \leq 100\). Floyd-Warshall is perfect.

for (int k = 0; k < N; k++)
    for (int i = 0; i < N; i++)
        for (int j = 0; j < N; j++)
            dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);

After this, dist[i][j] is the shortest distance from \(i\) to \(j\).


Step 2: Classify Each Edge

For edge \((u, v)\) with weight \(w\), three cases:

Case 1: \(\text{dist}[u][v] < w\). A shorter path exists. This edge is never on any shortest path. It's dead weight.

Case 2: \(\text{dist}[u][v] = w\) and no intermediate vertex achieves the same distance. This edge is the only way to achieve the shortest path between \(u\) and \(v\). It's essential.

Case 3: \(\text{dist}[u][v] = w\) and some vertex \(k\) satisfies \(\text{dist}[u][k] + \text{dist}[k][v] = w\). The edge ties with a multi-hop path. It's on some shortest paths but is redundant.

Edges in Cases 1 and 3 are "not candidates for shortest paths." Count them.

int ans = 0;
for (auto& [u, v, w] : edges) {
    if (dist[u][v] < w) { ans++; continue; }

    bool redundant = false;
    for (int k = 0; k < N; k++) {
        if (k == u || k == v) continue;
        if (dist[u][k] + dist[k][v] == w) {
            redundant = true;
            break;
        }
    }
    if (redundant) ans++;
}

Key insight. An edge \((u, v, w)\) is on a shortest path only if \(\text{dist}[u][v] = w\) and no shorter-hop route matches. The second condition is what separates necessary from redundant.


Trace: 5-Node Example

Graph with 5 nodes (0-4) and 7 edges:

Edge u v w
e0 0 1 2
e1 0 2 4
e2 1 2 1
e3 1 3 5
e4 2 3 2
e5 2 4 3
e6 3 4 1

Floyd-Warshall distance matrix:

0 1 2 3 4
0 0 2 3 5 6
1 2 0 1 3 4
2 3 1 0 2 3
3 5 3 2 0 1
4 6 4 3 1 0

Edge classification:

Edge w dist[u][v] Check Verdict
e0: (0,1) 2 2 No k gives dist[0][k]+dist[k][1]=2 Necessary
e1: (0,2) 4 3 dist < w Dead
e2: (1,2) 1 1 No k gives dist[1][k]+dist[k][2]=1 Necessary
e3: (1,3) 5 3 dist < w Dead
e4: (2,3) 2 2 No k gives dist[2][k]+dist[k][3]=2 Necessary
e5: (2,4) 3 3 k=3: dist[2][3]+dist[3][4]=2+1=3 Redundant
e6: (3,4) 1 1 No k gives dist[3][k]+dist[k][4]=1 Necessary

Answer: 3 edges are not on any shortest path (e1, e3, e5).

Gotcha. Edge e5 has \(w = \text{dist}[2][4] = 3\), so it is a shortest path. But the route \(2 \to 3 \to 4\) also has length 3. The edge is redundant, not dead. The distinction matters.


Variant: ABC243-E Edge Deletion

Same setup, different question. Given a weighted undirected graph, find the maximum number of edges you can delete while preserving all pairwise shortest distances.

The logic is almost identical, with one subtle difference.

An edge \((u, v, w)\) is deletable if there exists any vertex \(k\) such that \(\text{dist}[u][k] + \text{dist}[k][v] \leq w\).

int deletable = 0;
for (auto& [u, v, w] : edges) {
    bool can_delete = false;
    for (int k = 0; k < N; k++) {
        if (k == u || k == v) continue;
        if (dist[u][k] + dist[k][v] <= w) {
            can_delete = true;
            break;
        }
    }
    if (can_delete) deletable++;
}
cout << deletable << "\n";

Gotcha. Notice the \(\leq\) instead of \(=\). In ABC051-D we check \(= w\) to detect redundancy. Here we check \(\leq w\) because even a strictly shorter alternate path makes the edge deletable. An edge with \(\text{dist}[u][v] < w\) is trivially deletable (it was never useful). An edge with \(\text{dist}[u][v] = w\) but an alternate route matching it is also deletable.


Why Floyd-Warshall?

Both problems have \(N \leq 300\). Floyd-Warshall runs in \(O(N^3)\) and gives all-pairs distances in one pass. Dijkstra from every node would work too (\(O(N \cdot M \log N)\)), but Floyd is simpler here.

The edge classification loop is \(O(M \cdot N)\). Total: \(O(N^3 + MN)\).


The Edge Forensics Framework

For any edge \((u, v, w)\) in a graph where all shortest distances are known:

Condition Meaning
\(\text{dist}[u][v] < w\) Edge is never on any shortest path
\(\text{dist}[u][v] = w\), no bypass exists Edge is essential for some shortest path
\(\text{dist}[u][v] = w\), bypass via \(k\) exists Edge is redundant (on some shortest paths, but removable)

This framework applies to any problem that asks "which edges matter?"


Two classic problems ask about edge necessity from a connectivity perspective, not shortest paths:

  • Bridges (CSES Necessary Roads): edges whose removal disconnects the graph. Found by Tarjan's DFS in \(O(V + E)\).
  • Articulation Points (CSES Necessary Cities): vertices whose removal disconnects the graph. Same DFS approach.

These are covered in Chapter 14 (SCC and 2-SAT). The technique is different (DFS low-link values, not Floyd-Warshall), but the question is the same: which parts of the graph are structurally irreplaceable?


LC 1334: Find the City With the Smallest Number of Neighbors at a Threshold Distance

Floyd-Warshall, then for each city count how many other cities are reachable within distance threshold. Return the city with the smallest count (break ties by largest index).

This is a direct application of all-pairs shortest paths. No edge forensics needed, but it reinforces the Floyd-Warshall pattern.

int best_city = -1, best_count = N + 1;
for (int i = 0; i < N; i++) {
    int count = 0;
    for (int j = 0; j < N; j++) {
        if (i != j && dist[i][j] <= threshold) count++;
    }
    if (count <= best_count) {
        best_count = count;
        best_city = i;
    }
}

Full Solution: ABC243-E

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int main() {
    int N, M;
    cin >> N >> M;

    vector<vector<ll>> dist(N, vector<ll>(N, 1e18));
    for (int i = 0; i < N; i++) dist[i][i] = 0;

    vector<array<int,3>> edges(M);
    for (int i = 0; i < M; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        u--; v--;
        edges[i] = {u, v, w};
        dist[u][v] = min(dist[u][v], (ll)w);
        dist[v][u] = min(dist[v][u], (ll)w);
    }

    for (int k = 0; k < N; k++)
        for (int i = 0; i < N; i++)
            for (int j = 0; j < N; j++)
                dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);

    int deletable = 0;
    for (auto& [u, v, w] : edges) {
        bool can_delete = false;
        for (int k = 0; k < N; k++) {
            if (k == u || k == v) continue;
            if (dist[u][k] + dist[k][v] <= w) {
                can_delete = true;
                break;
            }
        }
        if (can_delete) deletable++;
    }

    cout << deletable << "\n";
    return 0;
}

Exercises

  1. Can an edge with \(\text{dist}[u][v] = w\) be on every shortest path from \(u\) to \(v\)? When?

  2. If the graph is a tree, how many edges are "candidates for no shortest path"? Why?

  3. In ABC243-E, what happens if you use \(<\) instead of \(\leq\) in the deletion check? Give a counterexample.


Problems

Problem Link Difficulty
ABC051-D -- Candidates of No Shortest Paths atcoder.jp/contests/abc051/tasks/abc051_d Medium
ABC243-E -- Edge Deletion atcoder.jp/contests/abc243/tasks/abc243_e Medium
LC 1334 -- Find the City leetcode.com/problems/find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance Medium