Skip to content

directed_mst.hpp

SECTIONGraph INCLUDEnoya/directed_mst.hpp

Minimum spanning arborescence rooted at root in O(m log n). Edges are (cost, from, to).

Verified by directedmst.

求指定根可达所有点的最小权有向生成树(最小树形图),并恢复入边选择。

Implementation

View on GitHub

#ifndef NOYA_DIRECTED_MST_HPP
#define NOYA_DIRECTED_MST_HPP 1

/// @complexity Time: O(E log V).
/// Space: O(V + E).

#include <cassert>
#include <numeric>
#include <optional>
#include <tuple>
#include <utility>
#include <vector>

namespace noya {

namespace directed_mst_internal {

template <class T> struct solver {
  struct edge {
    T cost{};
    int from = -1;
    int id = -1;
  };

  struct heap_node {
    heap_node *left = nullptr;
    heap_node *right = nullptr;
    edge value;
    T lazy{};
    int rank = 1;
  };

  int n;
  int root;
  const std::vector<std::tuple<T, int, int>> &edges;
  std::vector<heap_node> pool;
  int pool_index = 0;

  solver(int n_, int root_, const std::vector<std::tuple<T, int, int>> &edges_)
      : n(n_), root(root_), edges(edges_), pool(edges.size()) {}

  static int heap_rank(heap_node *node) {
    return node == nullptr ? 0 : node->rank;
  }

  static heap_node *add(heap_node *node, const T &delta) {
    if (node != nullptr) {
      node->value.cost += delta;
      node->lazy += delta;
    }
    return node;
  }

  static void push(heap_node *node) {
    if (node == nullptr) {
      return;
    }
    add(node->left, node->lazy);
    add(node->right, node->lazy);
    node->lazy = T{};
  }

  static heap_node *meld(heap_node *first, heap_node *second) {
    if (first == nullptr) {
      return second;
    }
    if (second == nullptr) {
      return first;
    }
    if (std::pair(second->value.cost, second->value.id) <
        std::pair(first->value.cost, first->value.id)) {
      std::swap(first, second);
    }
    push(first);
    first->right = meld(first->right, second);
    if (heap_rank(first->left) < heap_rank(first->right)) {
      std::swap(first->left, first->right);
    }
    first->rank = heap_rank(first->right) + 1;
    return first;
  }

  heap_node *make_node(T cost, int from, int id) {
    heap_node *node = &pool[pool_index++];
    node->value = {cost, from, id};
    return node;
  }

  static edge pop(heap_node *&node) {
    push(node);
    edge result = node->value;
    node = meld(node->left, node->right);
    return result;
  }

  std::optional<std::pair<T, std::vector<int>>> run() {
    assert(n > 0);
    assert(0 <= root && root < n);
    const int max_components = 2 * n;
    std::vector<heap_node *> incoming(max_components, nullptr);
    for (int id = 0; id < int(edges.size()); id++) {
      auto [cost, from, to] = edges[id];
      assert(0 <= from && from < n);
      assert(0 <= to && to < n);
      if (from != to) {
        incoming[to] = meld(incoming[to], make_node(cost, from, id));
      }
    }

    std::vector<int> dsu(max_components);
    std::iota(dsu.begin(), dsu.end(), 0);
    auto find = [&](int vertex) {
      int representative = vertex;
      while (dsu[representative] != representative) {
        representative = dsu[representative];
      }
      while (dsu[vertex] != vertex) {
        int next = dsu[vertex];
        dsu[vertex] = representative;
        vertex = next;
      }
      return representative;
    };

    std::vector<char> state(max_components);
    std::vector<edge> chosen(max_components);
    std::vector<int> contraction_parent(max_components, -1);
    std::vector<int> representative(max_components);
    std::iota(representative.begin(), representative.end(), 0);
    state[root] = 2;
    int component_count = n;

    for (int start = 0; start < n; start++) {
      if (state[start] != 0) {
        continue;
      }
      std::vector<int> path = {start};
      while (true) {
        int component = path.back();
        state[component] = 1;

        edge best;
        bool found = false;
        while (incoming[component] != nullptr) {
          edge candidate = pop(incoming[component]);
          if (find(candidate.from) != find(component)) {
            best = candidate;
            found = true;
            break;
          }
        }
        if (!found) {
          return std::nullopt;
        }
        chosen[component] = best;
        int previous = representative[find(best.from)];
        if (state[previous] == 0) {
          path.push_back(previous);
          continue;
        }
        if (state[previous] == 2) {
          break;
        }

        int contracted = component_count++;
        assert(contracted < max_components);
        while (true) {
          int member = path.back();
          path.pop_back();
          incoming[contracted] =
              meld(incoming[contracted],
                   add(incoming[member], -chosen[member].cost));
          dsu[find(member)] = contracted;
          contraction_parent[member] = contracted;
          state[member] = 2;
          if (member == previous) {
            break;
          }
        }
        representative[find(contracted)] = contracted;
        path.push_back(contracted);
      }
      for (int component : path) {
        state[component] = 2;
      }
    }

    std::vector<char> expanded(component_count);
    expanded[root] = true;
    std::vector<int> edge_ids;
    for (int component = component_count - 1; component >= 0; component--) {
      if (expanded[component]) {
        continue;
      }
      int id = chosen[component].id;
      if (id == -1) {
        return std::nullopt;
      }
      edge_ids.push_back(id);
      int vertex = std::get<2>(edges[id]);
      while (vertex != -1 && !expanded[vertex]) {
        expanded[vertex] = true;
        vertex = contraction_parent[vertex];
      }
    }
    if (int(edge_ids.size()) != n - 1) {
      return std::nullopt;
    }
    T cost{};
    for (int id : edge_ids) {
      cost += std::get<0>(edges[id]);
    }
    return std::pair<T, std::vector<int>>{cost, std::move(edge_ids)};
  }
};

} // namespace directed_mst_internal

/// @brief Minimum spanning arborescence rooted at root in O(m log n).
/// Edges are (cost, from, to).
/// @return (cost, edge indices), or nullopt if some vertex is unreachable.
template <class T>
std::optional<std::pair<T, std::vector<int>>>
directed_mst(int n, int root,
             const std::vector<std::tuple<T, int, int>> &edges) {
  return directed_mst_internal::solver<T>(n, root, edges).run();
}

} // namespace noya

#endif // NOYA_DIRECTED_MST_HPP
#include <cassert>
#include <numeric>
#include <optional>
#include <tuple>
#include <utility>
#include <vector>

/// @complexity Time: O(E log V).
/// Space: O(V + E).

namespace noya {

namespace directed_mst_internal {

template <class T> struct solver {
  struct edge {
    T cost{};
    int from = -1;
    int id = -1;
  };

  struct heap_node {
    heap_node *left = nullptr;
    heap_node *right = nullptr;
    edge value;
    T lazy{};
    int rank = 1;
  };

  int n;
  int root;
  const std::vector<std::tuple<T, int, int>> &edges;
  std::vector<heap_node> pool;
  int pool_index = 0;

  solver(int n_, int root_, const std::vector<std::tuple<T, int, int>> &edges_)
      : n(n_), root(root_), edges(edges_), pool(edges.size()) {}

  static int heap_rank(heap_node *node) {
    return node == nullptr ? 0 : node->rank;
  }

  static heap_node *add(heap_node *node, const T &delta) {
    if (node != nullptr) {
      node->value.cost += delta;
      node->lazy += delta;
    }
    return node;
  }

  static void push(heap_node *node) {
    if (node == nullptr) {
      return;
    }
    add(node->left, node->lazy);
    add(node->right, node->lazy);
    node->lazy = T{};
  }

  static heap_node *meld(heap_node *first, heap_node *second) {
    if (first == nullptr) {
      return second;
    }
    if (second == nullptr) {
      return first;
    }
    if (std::pair(second->value.cost, second->value.id) <
        std::pair(first->value.cost, first->value.id)) {
      std::swap(first, second);
    }
    push(first);
    first->right = meld(first->right, second);
    if (heap_rank(first->left) < heap_rank(first->right)) {
      std::swap(first->left, first->right);
    }
    first->rank = heap_rank(first->right) + 1;
    return first;
  }

  heap_node *make_node(T cost, int from, int id) {
    heap_node *node = &pool[pool_index++];
    node->value = {cost, from, id};
    return node;
  }

  static edge pop(heap_node *&node) {
    push(node);
    edge result = node->value;
    node = meld(node->left, node->right);
    return result;
  }

  std::optional<std::pair<T, std::vector<int>>> run() {
    assert(n > 0);
    assert(0 <= root && root < n);
    const int max_components = 2 * n;
    std::vector<heap_node *> incoming(max_components, nullptr);
    for (int id = 0; id < int(edges.size()); id++) {
      auto [cost, from, to] = edges[id];
      assert(0 <= from && from < n);
      assert(0 <= to && to < n);
      if (from != to) {
        incoming[to] = meld(incoming[to], make_node(cost, from, id));
      }
    }

    std::vector<int> dsu(max_components);
    std::iota(dsu.begin(), dsu.end(), 0);
    auto find = [&](int vertex) {
      int representative = vertex;
      while (dsu[representative] != representative) {
        representative = dsu[representative];
      }
      while (dsu[vertex] != vertex) {
        int next = dsu[vertex];
        dsu[vertex] = representative;
        vertex = next;
      }
      return representative;
    };

    std::vector<char> state(max_components);
    std::vector<edge> chosen(max_components);
    std::vector<int> contraction_parent(max_components, -1);
    std::vector<int> representative(max_components);
    std::iota(representative.begin(), representative.end(), 0);
    state[root] = 2;
    int component_count = n;

    for (int start = 0; start < n; start++) {
      if (state[start] != 0) {
        continue;
      }
      std::vector<int> path = {start};
      while (true) {
        int component = path.back();
        state[component] = 1;

        edge best;
        bool found = false;
        while (incoming[component] != nullptr) {
          edge candidate = pop(incoming[component]);
          if (find(candidate.from) != find(component)) {
            best = candidate;
            found = true;
            break;
          }
        }
        if (!found) {
          return std::nullopt;
        }
        chosen[component] = best;
        int previous = representative[find(best.from)];
        if (state[previous] == 0) {
          path.push_back(previous);
          continue;
        }
        if (state[previous] == 2) {
          break;
        }

        int contracted = component_count++;
        assert(contracted < max_components);
        while (true) {
          int member = path.back();
          path.pop_back();
          incoming[contracted] =
              meld(incoming[contracted],
                   add(incoming[member], -chosen[member].cost));
          dsu[find(member)] = contracted;
          contraction_parent[member] = contracted;
          state[member] = 2;
          if (member == previous) {
            break;
          }
        }
        representative[find(contracted)] = contracted;
        path.push_back(contracted);
      }
      for (int component : path) {
        state[component] = 2;
      }
    }

    std::vector<char> expanded(component_count);
    expanded[root] = true;
    std::vector<int> edge_ids;
    for (int component = component_count - 1; component >= 0; component--) {
      if (expanded[component]) {
        continue;
      }
      int id = chosen[component].id;
      if (id == -1) {
        return std::nullopt;
      }
      edge_ids.push_back(id);
      int vertex = std::get<2>(edges[id]);
      while (vertex != -1 && !expanded[vertex]) {
        expanded[vertex] = true;
        vertex = contraction_parent[vertex];
      }
    }
    if (int(edge_ids.size()) != n - 1) {
      return std::nullopt;
    }
    T cost{};
    for (int id : edge_ids) {
      cost += std::get<0>(edges[id]);
    }
    return std::pair<T, std::vector<int>>{cost, std::move(edge_ids)};
  }
};

} // namespace directed_mst_internal

/// @brief Minimum spanning arborescence rooted at root in O(m log n).
/// Edges are (cost, from, to).
/// @return (cost, edge indices), or nullopt if some vertex is unreachable.
template <class T>
std::optional<std::pair<T, std::vector<int>>>
directed_mst(int n, int root,
             const std::vector<std::tuple<T, int, int>> &edges) {
  return directed_mst_internal::solver<T>(n, root, edges).run();
}

} // namespace noya