Alex's Anthology of Algorithms Common Code for Contests in Concise C++
Graphs / Spanning Trees

4.4.3 Minimum Spanning Arborescence (Edmonds)

4-Graphs/4.4.3_Minimum_Spanning_Arborescence_(Edmonds).cpp

View on GitHub

A spanning arborescence rooted at a node root of a weighted, directed graph is a set of edges forming a tree in which every other node is reachable from the root along directed edges, that is, every node except the root has exactly one incoming edge. The minimum spanning arborescence minimizes the total edge weight, and is the directed analog of the minimum spanning tree.

Edmonds' (a.k.a. the Chu-Liu/Edmonds) algorithm computes the arborescence by first selecting, for each non-root node, its cheapest incoming edge. If these choices form no cycle, they already constitute the answer. Otherwise each cycle is contracted into a single supernode, and every edge entering the cycle has its weight reduced by the cost of the edge it would replace inside the cycle. Solving the problem on the contracted graph and expanding the cycles back yields the optimum. The implementation below repeats this contraction in rounds, accumulating the selected weights, until no cycle remains.

  • directed_mst(n, root, edges) returns the total weight of the minimum spanning arborescence rooted at root over n nodes numbered [0, n), or $-1$ if some node is unreachable from the root (no arborescence exists). Edges are given as (from, to, weight) triples; parallel edges and self-loops are allowed, with self-loops simply ignored.

Implementation

#include <cstdint>
#include <vector>

struct Edge {
  int from, to;
  int64_t weight;
};

int64_t directed_mst(int n, int root, std::vector<Edge> edges) {
  const int64_t INF = INT64_MAX / 4;
  int64_t result = 0;
  while (true) {
    std::vector<int64_t> min_in(n, INF);
    std::vector<int> pre(n, -1);
    for (const Edge &e : edges) {
      if (e.from != e.to && e.weight < min_in[e.to]) {
        min_in[e.to] = e.weight;
        pre[e.to] = e.from;
      }
    }
    for (int v = 0; v < n; v++) {
      if (v != root && min_in[v] == INF) {
        return -1;  // Node v has no incoming edge, so it is unreachable.
      }
    }
    // Identify cycles formed by the chosen incoming edges, labeling each with an ID in `comp`.
    int cycles = 0;
    std::vector<int> comp(n, -1), seen(n, -1);
    min_in[root] = 0;
    for (int v = 0; v < n; v++) {
      result += min_in[v];
      int u = v;
      while (seen[u] != v && comp[u] == -1 && u != root) {
        seen[u] = v;
        u = pre[u];
      }
      if (u != root && comp[u] == -1) {
        for (int w = pre[u]; w != u; w = pre[w]) {
          comp[w] = cycles;
        }
        comp[u] = cycles++;
      }
    }
    if (cycles == 0) {
      break;  // No cycle remains; the selected edges form the arborescence.
    }
    // Contract each cycle into a supernode and reduce the weights of edges entering it.
    for (int v = 0; v < n; v++) {
      if (comp[v] == -1) {
        comp[v] = cycles++;
      }
    }
    for (Edge &e : edges) {
      int from = comp[e.from], to = comp[e.to];
      if (from != to) {
        e.weight -= min_in[e.to];
      }
      e.from = from;
      e.to = to;
    }
    n = cycles;
    root = comp[root];
  }
  return result;
}

Example Usage

#include <cassert>
using namespace std;

int main() {
  // Root 0. Cheapest incoming edges 1<-0 (1), 2<-1 (2), 3<-2 (3) form a tree of weight 6.
  vector<Edge> edges{
      {0, 1, 1}, {0, 2, 5}, {1, 2, 2}, {2, 3, 3}, {3, 1, 4},
  };
  assert(directed_mst(4, 0, edges) == 6);

  // Node 3 is unreachable from root 0.
  vector<Edge> disconnected{{0, 1, 1}, {1, 2, 1}};
  assert(directed_mst(4, 0, disconnected) == -1);
  return 0;
}