Skip to content

steiner_tree.hpp

SECTIONGraph INCLUDEnoya/steiner_tree.hpp

Construct a minimum Steiner tree in an undirected graph. Subset DP first joins two terminal groups at a common vertex, then a multi-source Dijkstra closure moves that root through the graph. Parent records replay both transitions and recover the selected original edge indices.

Verified by minimum_steiner_tree.

用子集 DP 求连接给定终端点的最小权 Steiner 树;适合终端数量较小的图上连通代价。

Implementation

View on GitHub

#ifndef NOYA_STEINER_TREE_HPP
#define NOYA_STEINER_TREE_HPP 1

/// @complexity Time: O(3^k V + 2^k E log V) for k terminals.
/// Space: O(2^k V).

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

namespace noya {

template <class Weight> struct steiner_tree_edge {
  int first;
  int second;
  Weight weight;
};

template <class Weight> struct steiner_tree_result {
  Weight weight;
  std::vector<int> edge_ids;
};

/// @brief Construct a minimum Steiner tree in an undirected graph. Subset DP
/// first joins two terminal groups at a common vertex, then a multi-source
/// Dijkstra closure moves that root through the graph. Parent records replay
/// both transitions and recover the selected original edge indices.
template <class Weight>
steiner_tree_result<Weight>
steiner_tree_with_edges(int n, const std::vector<steiner_tree_edge<Weight>> &edges,
                        std::vector<int> terminals) {
  assert(n >= 0);
  struct adjacent_edge {
    int next;
    Weight weight;
    int id;
  };
  std::vector<std::vector<adjacent_edge>> graph(n);
  for (int id = 0; id < int(edges.size()); id++) {
    auto [first, second, weight] = edges[id];
    assert(0 <= first && first < n && 0 <= second && second < n);
    assert(!(weight < Weight{}));
    graph[first].push_back({second, weight, id});
    graph[second].push_back({first, weight, id});
  }
  for (int terminal : terminals) {
    assert(0 <= terminal && terminal < n);
  }
  std::sort(terminals.begin(), terminals.end());
  terminals.erase(std::unique(terminals.begin(), terminals.end()),
                  terminals.end());
  const int terminal_count = int(terminals.size());
  assert(terminal_count < int(sizeof(unsigned) * 8));
  if (terminal_count == 0) {
    return {Weight{}, {}};
  }
  const Weight infinity = std::numeric_limits<Weight>::max();
  const int mask_count = 1 << terminal_count;
  struct parent_record {
    int subset = 0;
    int previous_vertex = -1;
    int edge_id = -1;
  };
  std::vector<std::vector<Weight>> distance(mask_count,
                                            std::vector<Weight>(n, infinity));
  std::vector<std::vector<parent_record>> parent(
      mask_count, std::vector<parent_record>(n));
  for (int id = 0; id < terminal_count; id++) {
    distance[1 << id][terminals[id]] = Weight{};
  }

  for (int mask = 1; mask < mask_count; mask++) {
    for (int subset = (mask - 1) & mask; subset > 0;
         subset = (subset - 1) & mask) {
      int other = mask ^ subset;
      if (subset > other) {
        continue;
      }
      for (int vertex = 0; vertex < n; vertex++) {
        if (distance[subset][vertex] == infinity ||
            distance[other][vertex] == infinity) {
          continue;
        }
        Weight candidate = distance[subset][vertex] + distance[other][vertex];
        if (candidate < distance[mask][vertex]) {
          distance[mask][vertex] = candidate;
          parent[mask][vertex] = {subset, -1, -1};
        }
      }
    }

    using state = std::pair<Weight, int>;
    std::priority_queue<state, std::vector<state>, std::greater<>> queue;
    for (int vertex = 0; vertex < n; vertex++) {
      if (distance[mask][vertex] != infinity) {
        queue.emplace(distance[mask][vertex], vertex);
      }
    }
    while (!queue.empty()) {
      auto [current, vertex] = queue.top();
      queue.pop();
      if (current != distance[mask][vertex]) {
        continue;
      }
      for (auto edge : graph[vertex]) {
        if (current <= infinity - edge.weight &&
            current + edge.weight < distance[mask][edge.next]) {
          distance[mask][edge.next] = current + edge.weight;
          parent[mask][edge.next] = {0, vertex, edge.id};
          queue.emplace(distance[mask][edge.next], edge.next);
        }
      }
    }
  }

  int root = int(std::min_element(distance.back().begin(),
                                  distance.back().end()) -
                 distance.back().begin());
  if (distance.back()[root] == infinity) {
    return {infinity, {}};
  }
  std::vector<bool> used(edges.size());
  auto recover = [&](auto &&self, int mask, int vertex) -> void {
    parent_record record = parent[mask][vertex];
    if (record.edge_id != -1) {
      self(self, mask, record.previous_vertex);
      used[record.edge_id] = true;
    } else if (record.subset != 0) {
      self(self, record.subset, vertex);
      self(self, mask ^ record.subset, vertex);
    }
  };
  recover(recover, mask_count - 1, root);
  std::vector<int> selected;
  Weight selected_weight{};
  for (int id = 0; id < int(edges.size()); id++) {
    if (used[id]) {
      selected.push_back(id);
      selected_weight += edges[id].weight;
    }
  }
  assert(selected_weight == distance.back()[root]);
  return {selected_weight, std::move(selected)};
}

/// @brief Minimum weight of a connected subgraph containing all terminals in
/// an undirected nonnegative-weight graph, using O(3^k n + 2^k m log n).
template <class Weight>
Weight
steiner_tree(const std::vector<std::vector<std::pair<int, Weight>>> &graph,
             std::vector<int> terminals) {
  const int n = int(graph.size());
  for (int terminal : terminals) {
    assert(0 <= terminal && terminal < n);
  }
  for (int vertex = 0; vertex < n; vertex++) {
    for (auto [next, weight] : graph[vertex]) {
      assert(0 <= next && next < n);
      assert(!(weight < Weight{}));
    }
  }
  std::sort(terminals.begin(), terminals.end());
  terminals.erase(std::unique(terminals.begin(), terminals.end()),
                  terminals.end());
  const int terminal_count = int(terminals.size());
  assert(terminal_count < int(sizeof(unsigned) * 8));
  if (terminal_count == 0) {
    return Weight{};
  }
  const Weight infinity = std::numeric_limits<Weight>::max();
  const int mask_count = 1 << terminal_count;
  std::vector<std::vector<Weight>> distance(mask_count,
                                            std::vector<Weight>(n, infinity));
  for (int id = 0; id < terminal_count; id++) {
    distance[1 << id][terminals[id]] = Weight{};
  }

  for (int mask = 1; mask < mask_count; mask++) {
    for (int subset = (mask - 1) & mask; subset > 0;
         subset = (subset - 1) & mask) {
      int other = mask ^ subset;
      if (subset > other) {
        continue;
      }
      for (int vertex = 0; vertex < n; vertex++) {
        if (distance[subset][vertex] == infinity ||
            distance[other][vertex] == infinity) {
          continue;
        }
        distance[mask][vertex] =
            std::min(distance[mask][vertex],
                     distance[subset][vertex] + distance[other][vertex]);
      }
    }

    using state = std::pair<Weight, int>;
    std::priority_queue<state, std::vector<state>, std::greater<>> queue;
    for (int vertex = 0; vertex < n; vertex++) {
      if (distance[mask][vertex] != infinity) {
        queue.emplace(distance[mask][vertex], vertex);
      }
    }
    while (!queue.empty()) {
      auto [current, vertex] = queue.top();
      queue.pop();
      if (current != distance[mask][vertex]) {
        continue;
      }
      for (auto [next, weight] : graph[vertex]) {
        if (current <= infinity - weight &&
            current + weight < distance[mask][next]) {
          distance[mask][next] = current + weight;
          queue.emplace(distance[mask][next], next);
        }
      }
    }
  }
  return *std::min_element(distance.back().begin(), distance.back().end());
}

} // namespace noya

#endif // NOYA_STEINER_TREE_HPP
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <limits>
#include <queue>
#include <utility>
#include <vector>

/// @complexity Time: O(3^k V + 2^k E log V) for k terminals.
/// Space: O(2^k V).

namespace noya {

template <class Weight> struct steiner_tree_edge {
  int first;
  int second;
  Weight weight;
};

template <class Weight> struct steiner_tree_result {
  Weight weight;
  std::vector<int> edge_ids;
};

/// @brief Construct a minimum Steiner tree in an undirected graph. Subset DP
/// first joins two terminal groups at a common vertex, then a multi-source
/// Dijkstra closure moves that root through the graph. Parent records replay
/// both transitions and recover the selected original edge indices.
template <class Weight>
steiner_tree_result<Weight>
steiner_tree_with_edges(int n, const std::vector<steiner_tree_edge<Weight>> &edges,
                        std::vector<int> terminals) {
  assert(n >= 0);
  struct adjacent_edge {
    int next;
    Weight weight;
    int id;
  };
  std::vector<std::vector<adjacent_edge>> graph(n);
  for (int id = 0; id < int(edges.size()); id++) {
    auto [first, second, weight] = edges[id];
    assert(0 <= first && first < n && 0 <= second && second < n);
    assert(!(weight < Weight{}));
    graph[first].push_back({second, weight, id});
    graph[second].push_back({first, weight, id});
  }
  for (int terminal : terminals) {
    assert(0 <= terminal && terminal < n);
  }
  std::sort(terminals.begin(), terminals.end());
  terminals.erase(std::unique(terminals.begin(), terminals.end()),
                  terminals.end());
  const int terminal_count = int(terminals.size());
  assert(terminal_count < int(sizeof(unsigned) * 8));
  if (terminal_count == 0) {
    return {Weight{}, {}};
  }
  const Weight infinity = std::numeric_limits<Weight>::max();
  const int mask_count = 1 << terminal_count;
  struct parent_record {
    int subset = 0;
    int previous_vertex = -1;
    int edge_id = -1;
  };
  std::vector<std::vector<Weight>> distance(mask_count,
                                            std::vector<Weight>(n, infinity));
  std::vector<std::vector<parent_record>> parent(
      mask_count, std::vector<parent_record>(n));
  for (int id = 0; id < terminal_count; id++) {
    distance[1 << id][terminals[id]] = Weight{};
  }

  for (int mask = 1; mask < mask_count; mask++) {
    for (int subset = (mask - 1) & mask; subset > 0;
         subset = (subset - 1) & mask) {
      int other = mask ^ subset;
      if (subset > other) {
        continue;
      }
      for (int vertex = 0; vertex < n; vertex++) {
        if (distance[subset][vertex] == infinity ||
            distance[other][vertex] == infinity) {
          continue;
        }
        Weight candidate = distance[subset][vertex] + distance[other][vertex];
        if (candidate < distance[mask][vertex]) {
          distance[mask][vertex] = candidate;
          parent[mask][vertex] = {subset, -1, -1};
        }
      }
    }

    using state = std::pair<Weight, int>;
    std::priority_queue<state, std::vector<state>, std::greater<>> queue;
    for (int vertex = 0; vertex < n; vertex++) {
      if (distance[mask][vertex] != infinity) {
        queue.emplace(distance[mask][vertex], vertex);
      }
    }
    while (!queue.empty()) {
      auto [current, vertex] = queue.top();
      queue.pop();
      if (current != distance[mask][vertex]) {
        continue;
      }
      for (auto edge : graph[vertex]) {
        if (current <= infinity - edge.weight &&
            current + edge.weight < distance[mask][edge.next]) {
          distance[mask][edge.next] = current + edge.weight;
          parent[mask][edge.next] = {0, vertex, edge.id};
          queue.emplace(distance[mask][edge.next], edge.next);
        }
      }
    }
  }

  int root = int(std::min_element(distance.back().begin(),
                                  distance.back().end()) -
                 distance.back().begin());
  if (distance.back()[root] == infinity) {
    return {infinity, {}};
  }
  std::vector<bool> used(edges.size());
  auto recover = [&](auto &&self, int mask, int vertex) -> void {
    parent_record record = parent[mask][vertex];
    if (record.edge_id != -1) {
      self(self, mask, record.previous_vertex);
      used[record.edge_id] = true;
    } else if (record.subset != 0) {
      self(self, record.subset, vertex);
      self(self, mask ^ record.subset, vertex);
    }
  };
  recover(recover, mask_count - 1, root);
  std::vector<int> selected;
  Weight selected_weight{};
  for (int id = 0; id < int(edges.size()); id++) {
    if (used[id]) {
      selected.push_back(id);
      selected_weight += edges[id].weight;
    }
  }
  assert(selected_weight == distance.back()[root]);
  return {selected_weight, std::move(selected)};
}

/// @brief Minimum weight of a connected subgraph containing all terminals in
/// an undirected nonnegative-weight graph, using O(3^k n + 2^k m log n).
template <class Weight>
Weight
steiner_tree(const std::vector<std::vector<std::pair<int, Weight>>> &graph,
             std::vector<int> terminals) {
  const int n = int(graph.size());
  for (int terminal : terminals) {
    assert(0 <= terminal && terminal < n);
  }
  for (int vertex = 0; vertex < n; vertex++) {
    for (auto [next, weight] : graph[vertex]) {
      assert(0 <= next && next < n);
      assert(!(weight < Weight{}));
    }
  }
  std::sort(terminals.begin(), terminals.end());
  terminals.erase(std::unique(terminals.begin(), terminals.end()),
                  terminals.end());
  const int terminal_count = int(terminals.size());
  assert(terminal_count < int(sizeof(unsigned) * 8));
  if (terminal_count == 0) {
    return Weight{};
  }
  const Weight infinity = std::numeric_limits<Weight>::max();
  const int mask_count = 1 << terminal_count;
  std::vector<std::vector<Weight>> distance(mask_count,
                                            std::vector<Weight>(n, infinity));
  for (int id = 0; id < terminal_count; id++) {
    distance[1 << id][terminals[id]] = Weight{};
  }

  for (int mask = 1; mask < mask_count; mask++) {
    for (int subset = (mask - 1) & mask; subset > 0;
         subset = (subset - 1) & mask) {
      int other = mask ^ subset;
      if (subset > other) {
        continue;
      }
      for (int vertex = 0; vertex < n; vertex++) {
        if (distance[subset][vertex] == infinity ||
            distance[other][vertex] == infinity) {
          continue;
        }
        distance[mask][vertex] =
            std::min(distance[mask][vertex],
                     distance[subset][vertex] + distance[other][vertex]);
      }
    }

    using state = std::pair<Weight, int>;
    std::priority_queue<state, std::vector<state>, std::greater<>> queue;
    for (int vertex = 0; vertex < n; vertex++) {
      if (distance[mask][vertex] != infinity) {
        queue.emplace(distance[mask][vertex], vertex);
      }
    }
    while (!queue.empty()) {
      auto [current, vertex] = queue.top();
      queue.pop();
      if (current != distance[mask][vertex]) {
        continue;
      }
      for (auto [next, weight] : graph[vertex]) {
        if (current <= infinity - weight &&
            current + weight < distance[mask][next]) {
          distance[mask][next] = current + weight;
          queue.emplace(distance[mask][next], next);
        }
      }
    }
  }
  return *std::min_element(distance.back().begin(), distance.back().end());
}

} // namespace noya