For an undirected graph there is a weighted tree on the same vertices such that for every pair the minimum – cut equals the minimum edge weight on the tree path, and removing that edge splits the vertices into an actual minimum – cut. It takes max flows (Gusfield’s version needs no contraction). Consequences: at most distinct pairwise min-cut values, all-pairs queries by path minimum, edge connectivity = lightest tree edge. No such tree exists for directed graphs.

vector<array<ll, 3>> gomoryHu(int n, const vector<array<ll, 3>>& E) {   // {u, v, w}; returns tree edges {v, p, cut}
    vector<int> p(n, 0); vector<ll> fl(n);
    for (int s = 1; s < n; s++) {
        int t = p[s]; Dinic D(n);
        for (auto [u, v, w] : E) D.addEdge(u, v, w, w);
        ll f = D.maxflow(s, t); fl[s] = f;
        for (int i = 0; i < n; i++) if (i != s && D.sourceSide(i) && p[i] == t) p[i] = s;
        if (D.sourceSide(p[t])) p[s] = p[t], p[t] = s, fl[s] = fl[t], fl[t] = f;   // keeps the cut property
    }
    vector<array<ll, 3>> tree;
    for (int v = 1; v < n; v++) tree.push_back({v, p[v], fl[v]});
    return tree;
}

Dropping the last line of the loop (the swap) and the i < s updates still gives a correct flow-equivalent tree (right values, but tree-edge cuts may not be the real cuts). Swap Dinic for push–relabel on dense graphs.

Using the tree

  • Query : path minimum (binary lifting, or DFS per query for small ).
  • Sum of over all pairs: sort tree edges by decreasing weight and merge with a DSU; an edge joining components of sizes contributes .
  • Count pairs with : the same DSU, stopping at weight .
  • Minimum -cut approximation: remove the lightest tree edges; within a factor .
  • Minimum cut separating a vertex set (Steiner cut): the lightest tree edge whose removal splits .
ll allPairsMinCutSum(int n, vector<array<ll, 3>> tree) {
    sort(all(tree), [](auto& a, auto& b) { return a[2] > b[2]; });
    DSU d(n); ll s = 0;
    for (auto [u, v, w] : tree) s += w * d.size(u) * d.size(v), d.unite(u, v);
    return s;
}