Alex's Anthology of Algorithms Common Code for Contests in Concise C++
Graphs / Flows and Cuts

4.5.8 Gomory-Hu Tree

4-Graphs/4.5.8_Gomory-Hu_Tree.cpp

View on GitHub

Given an undirected graph with nonnegative edge weights, build a Gomory-Hu tree: a weighted tree on the same vertices that represents every pairwise minimum $s$-$t$ cut value. For any two vertices $u$ and $v$, the minimum cut value between them in the original graph is the minimum edge weight on the path from $u$ to $v$ in the Gomory-Hu tree.

The algorithm starts with every vertex attached to vertex 0, then performs $n - 1$ max-flow/min-cut computations. After computing the minimum cut between vertex $s$ and its current parent, vertices on the $s$ side of that cut are reparented under $s$, gradually refining the cut-equivalent tree.

  • gomory_hu(n, edges) returns the n - 1 edges of a Gomory-Hu tree as (u, v, weight) tripes for an undirected, weighted graph specificed by edges of the same shape.
  • min_cut_value(n, tree, source, sink) returns the minimum cut value between two vertices using a Gomory-Hu tree. Note that for many pairwise cut queries on the same tree, it's more efficient to prebuild the tree adjacency once and answer minimum edge-on-path queries with LCA/binary lifting instead of calling min_cut_value() each time.

Implementation

#include <algorithm>
#include <cassert>
#include <limits>
#include <queue>
#include <tuple>
#include <utility>
#include <vector>

template<class T>
class Dinic {
  int nodes;
  std::vector<std::vector<std::tuple<int, int, T>>> adj;  // (v, rev, cap)
  std::vector<int> dist, ptr;

  bool bfs(int source, int sink) {
    dist.assign(nodes, -1);
    dist[source] = 0;
    std::queue<int> q;
    q.push(source);
    while (!q.empty()) {
      int u = q.front();
      q.pop();
      for (const auto &[v, rev, cap] : adj[u]) {
        if (dist[v] < 0 && cap > 0) {
          dist[v] = dist[u] + 1;
          q.push(v);
        }
      }
    }
    return dist[sink] >= 0;
  }

  T dfs(int u, int sink, T pushed) {
    if (u == sink || pushed == 0) {
      return pushed;
    }
    for (; ptr[u] < static_cast<int>(adj[u].size()); ptr[u]++) {
      auto &[v, rev, cap] = adj[u][ptr[u]];
      if (dist[v] == dist[u] + 1 && cap > 0) {
        T pushed_next = dfs(v, sink, std::min(pushed, cap));
        if (pushed_next > 0) {
          cap -= pushed_next;
          std::get<2>(adj[v][rev]) += pushed_next;
          return pushed_next;
        }
      }
    }
    return 0;
  }

 public:
  explicit Dinic(int n) : nodes(n), adj(n), dist(n), ptr(n) {}

  void add_edge(int u, int v, T cap) {
    adj[u].emplace_back(v, static_cast<int>(adj[v].size()), cap);
    adj[v].emplace_back(u, static_cast<int>(adj[u].size()) - 1, 0);
  }

  T max_flow(int source, int sink) {
    T flow = 0;
    while (bfs(source, sink)) {
      ptr.assign(nodes, 0);
      while (true) {
        T pushed = dfs(source, sink, std::numeric_limits<T>::max());
        if (pushed == 0) {
          break;
        }
        flow += pushed;
      }
    }
    return flow;
  }

  std::vector<bool> min_cut(int source) const {
    std::vector<bool> reachable(nodes);
    std::queue<int> q;
    reachable[source] = true;
    q.push(source);
    while (!q.empty()) {
      int u = q.front();
      q.pop();
      for (const auto &[v, rev, cap] : adj[u]) {
        if (!reachable[v] && cap > 0) {
          reachable[v] = true;
          q.push(v);
        }
      }
    }
    return reachable;
  }
};

template<class T>
using Edge = std::tuple<int, int, T>;

template<class T>
std::vector<Edge<T>> gomory_hu(int n, const std::vector<Edge<T>> &edges) {
  assert(n >= 1);
  std::vector<int> parent(n, 0);
  std::vector<T> cut_value(n);
  for (int s = 1; s < n; s++) {
    int t = parent[s];
    Dinic<T> mf(n);
    for (const auto &[u, v, w] : edges) {
      mf.add_edge(u, v, w);
      mf.add_edge(v, u, w);
    }
    T flow = mf.max_flow(s, t);
    std::vector<bool> cut = mf.min_cut(s);
    for (int v = s + 1; v < n; v++) {
      if (parent[v] == t && cut[v] == cut[s]) {
        parent[v] = s;
      }
    }
    if (cut[parent[t]]) {
      parent[s] = parent[t];
      parent[t] = s;
      cut_value[s] = cut_value[t];
      cut_value[t] = flow;
    } else {
      cut_value[s] = flow;
    }
  }
  std::vector<Edge<T>> tree;
  for (int v = 1; v < n; v++) {
    tree.emplace_back(v, parent[v], cut_value[v]);
  }
  return tree;
}

template<class T>
T min_cut_value_dfs(
    const std::vector<std::vector<std::pair<int, T>>> &adj, std::vector<bool> &seen, int u,
    int sink, T best
) {
  if (u == sink) {
    return best;
  }
  seen[u] = true;
  for (auto [v, w] : adj[u]) {
    if (!seen[v]) {
      T res = min_cut_value_dfs(adj, seen, v, sink, std::min(best, w));
      if (res != -1) {
        return res;
      }
    }
  }
  return -1;
}

template<class T>
T min_cut_value(int n, const std::vector<Edge<T>> &tree, int source, int sink) {
  assert(0 <= source && source < n && 0 <= sink && sink < n && source != sink);
  // For many queries, prebuild this adjacency and add LCA/binary lifting for min edge on path.
  std::vector<std::vector<std::pair<int, T>>> adj(n);
  for (const auto &[u, v, w] : tree) {
    adj[u].emplace_back(v, w);
    adj[v].emplace_back(u, w);
  }
  std::vector<bool> seen(n);
  return min_cut_value_dfs(adj, seen, source, sink, std::numeric_limits<T>::max());
}

Example Usage

#include <cassert>
using namespace std;

int main() {
  vector<Edge<int>> edges{
      {0, 1, 3}, {0, 2, 2}, {1, 2, 1}, {1, 3, 2}, {2, 3, 4},
  };
  auto tree = gomory_hu(4, edges);
  assert(tree.size() == 3);
  assert(min_cut_value(4, tree, 0, 1) == 5);
  assert(min_cut_value(4, tree, 0, 2) == 5);
  assert(min_cut_value(4, tree, 0, 3) == 5);
  assert(min_cut_value(4, tree, 2, 3) == 6);
  return 0;
}