Minimum Cost Path with Edge Reversals

medium graph theory dijkstra shortest path

Problem

You have a directed weighted graph with n nodes. Each edge [u, v, w] goes from u to v with cost w. When you arrive at a node, you may reverse one of its incoming edges once: an original edge v → u can be traversed as u → v, but a reversed traversal costs 2·w. Return the minimum total cost from node 0 to node n − 1, or −1 if unreachable.

Key insight: reversing edge [u, v, w] is equivalent to having an extra edge v → u with cost 2·w always available. Add those reverse edges, then run Dijkstra.

Inputn = 4, edges = [[0,1,3],[3,1,1],[2,3,4],[0,2,2]]
Output5
Take 0 → 1 (cost 3), then reverse 3 → 1 into 1 → 3 at cost 2·1 = 2. Total = 5.

import heapq

def minCost(n, edges):
    adj = [[] for _ in range(n)]
    for u, v, w in edges:
        adj[u].append((v, w))        # original edge u -> v
        adj[v].append((u, 2 * w))    # reversed edge v -> u costs 2w
    dist = [float('inf')] * n
    dist[0] = 0
    pq = [(0, 0)]                     # (cost, node)
    while pq:
        d, u = heapq.heappop(pq)
        if d > dist[u]:              # stale entry, skip
            continue
        if u == n - 1:
            return d
        for v, w in adj[u]:          # relax neighbours
            if d + w < dist[v]:
                dist[v] = d + w
                heapq.heappush(pq, (dist[v], v))
    return -1
function minCost(n, edges) {
  const adj = Array.from({ length: n }, () => []);
  for (const [u, v, w] of edges) {
    adj[u].push([v, w]);          // original edge u -> v
    adj[v].push([u, 2 * w]);      // reversed edge v -> u costs 2w
  }
  const dist = new Array(n).fill(Infinity);
  dist[0] = 0;
  const pq = [[0, 0]];            // [cost, node] min-heap
  while (pq.length) {
    pq.sort((a, b) => a[0] - b[0]);
    const [d, u] = pq.shift();
    if (d > dist[u]) continue;    // stale entry, skip
    if (u === n - 1) return d;
    for (const [v, w] of adj[u]) {
      if (d + w < dist[v]) {
        dist[v] = d + w;
        pq.push([dist[v], v]);
      }
    }
  }
  return -1;
}
int minCost(int n, int[][] edges) {
    List<int[]>[] adj = new List[n];
    for (int i = 0; i < n; i++) adj[i] = new ArrayList<>();
    for (int[] e : edges) {
        adj[e[0]].add(new int[]{e[1], e[2]});       // u -> v
        adj[e[1]].add(new int[]{e[0], 2 * e[2]});   // v -> u costs 2w
    }
    long[] dist = new long[n];
    Arrays.fill(dist, Long.MAX_VALUE);
    dist[0] = 0;
    PriorityQueue<long[]> pq = new PriorityQueue<>((a, b) -> Long.compare(a[0], b[0]));
    pq.add(new long[]{0, 0});
    while (!pq.isEmpty()) {
        long[] top = pq.poll();
        long d = top[0]; int u = (int) top[1];
        if (d > dist[u]) continue;                  // stale entry
        if (u == n - 1) return (int) d;
        for (int[] nb : adj[u]) {
            if (d + nb[1] < dist[nb[0]]) {
                dist[nb[0]] = d + nb[1];
                pq.add(new long[]{dist[nb[0]], nb[0]});
            }
        }
    }
    return -1;
}
int minCost(int n, vector<vector<int>>& edges) {
    vector<vector<pair<int,int>>> adj(n);
    for (auto& e : edges) {
        adj[e[0]].push_back({e[1], e[2]});          // u -> v
        adj[e[1]].push_back({e[0], 2 * e[2]});      // v -> u costs 2w
    }
    vector<long long> dist(n, LLONG_MAX);
    dist[0] = 0;
    priority_queue<pair<long long,int>, vector<pair<long long,int>>, greater<>> pq;
    pq.push({0, 0});
    while (!pq.empty()) {
        auto [d, u] = pq.top(); pq.pop();
        if (d > dist[u]) continue;                  // stale entry
        if (u == n - 1) return (int) d;
        for (auto& [v, w] : adj[u]) {
            if (d + w < dist[v]) {
                dist[v] = d + w;
                pq.push({dist[v], v});
            }
        }
    }
    return -1;
}
Time: O((V + E) log V) Space: O(V + E)