Skip to content

steiner_tree.hpp

SECTIONGraph INCLUDEnoya/steiner_tree.hpp

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

Complexity: Time: O(3^k V + 2^k E log V) for k ter. Space: O(2^k V).

AC 记录:minimum_steiner_tree

跳到代码 · GitHub ↗

Implementation

当前头文件,省略 include guard;依赖见 #include

/// @complexity Time: O(3^k V + 2^k E log V) for k ter.
/// 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 a;
  int b;
  Weight w;
};

template <class Weight> struct steiner_tree_result {
  Weight w;
  std::vector<int> eid;
};

/// @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>> &es,
                        std::vector<int> ter) {
  assert(n >= 0);
  struct adjacent_edge {
    int nxt;
    Weight w;
    int id;
  };
  std::vector<std::vector<adjacent_edge>> g(n);
  for (int id = 0; id < int(es.size()); id++) {
    auto [a, b, w] = es[id];
    assert(0 <= a && a < n && 0 <= b && b < n);
    assert(!(w < Weight{}));
    g[a].push_back({b, w, id});
    g[b].push_back({a, w, id});
  }
  for (int t : ter) {
    assert(0 <= t && t < n);
  }
  std::sort(ter.begin(), ter.end());
  ter.erase(std::unique(ter.begin(), ter.end()), ter.end());
  const int k = int(ter.size());
  assert(k < int(sizeof(unsigned) * 8));
  if (k == 0) {
    return {Weight{}, {}};
  }
  const Weight inf = std::numeric_limits<Weight>::max();
  const int lim = 1 << k;
  struct parent_record {
    int sub = 0;
    int pv = -1;
    int ei1 = -1;
  };
  std::vector<std::vector<Weight>> dis(lim, std::vector<Weight>(n, inf));
  std::vector<std::vector<parent_record>> fa(lim,
                                             std::vector<parent_record>(n));
  for (int id = 0; id < k; id++) {
    dis[1 << id][ter[id]] = Weight{};
  }

  for (int msk = 1; msk < lim; msk++) {
    for (int sub = (msk - 1) & msk; sub > 0; sub = (sub - 1) & msk) {
      int rhs = msk ^ sub;
      if (sub > rhs) {
        continue;
      }
      for (int u = 0; u < n; u++) {
        if (dis[sub][u] == inf || dis[rhs][u] == inf) {
          continue;
        }
        Weight can = dis[sub][u] + dis[rhs][u];
        if (can < dis[msk][u]) {
          dis[msk][u] = can;
          fa[msk][u] = {sub, -1, -1};
        }
      }
    }

    using state = std::pair<Weight, int>;
    std::priority_queue<state, std::vector<state>, std::greater<>> q;
    for (int u = 0; u < n; u++) {
      if (dis[msk][u] != inf) {
        q.emplace(dis[msk][u], u);
      }
    }
    while (!q.empty()) {
      auto [cur, u] = q.top();
      q.pop();
      if (cur != dis[msk][u]) {
        continue;
      }
      for (auto e : g[u]) {
        if (cur <= inf - e.w && cur + e.w < dis[msk][e.nxt]) {
          dis[msk][e.nxt] = cur + e.w;
          fa[msk][e.nxt] = {0, u, e.id};
          q.emplace(dis[msk][e.nxt], e.nxt);
        }
      }
    }
  }

  int rt = int(std::min_element(dis.back().begin(), dis.back().end()) -
               dis.back().begin());
  if (dis.back()[rt] == inf) {
    return {inf, {}};
  }
  std::vector<bool> vis(es.size());
  auto get = [&](auto &&self, int msk, int u) -> void {
    parent_record rec = fa[msk][u];
    if (rec.ei1 != -1) {
      self(self, msk, rec.pv);
      vis[rec.ei1] = true;
    } else if (rec.sub != 0) {
      self(self, rec.sub, u);
      self(self, msk ^ rec.sub, u);
    }
  };
  get(get, lim - 1, rt);
  std::vector<int> sel;
  Weight sw{};
  for (int id = 0; id < int(es.size()); id++) {
    if (vis[id]) {
      sel.push_back(id);
      sw += es[id].w;
    }
  }
  assert(sw == dis.back()[rt]);
  return {sw, std::move(sel)};
}

/// @brief Minimum weight of a connected subgraph containing all ter 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>>> &g,
                    std::vector<int> ter) {
  const int n = int(g.size());
  for (int t : ter) {
    assert(0 <= t && t < n);
  }
  for (int u = 0; u < n; u++) {
    for (auto [nxt, w] : g[u]) {
      assert(0 <= nxt && nxt < n);
      assert(!(w < Weight{}));
    }
  }
  std::sort(ter.begin(), ter.end());
  ter.erase(std::unique(ter.begin(), ter.end()), ter.end());
  const int k = int(ter.size());
  assert(k < int(sizeof(unsigned) * 8));
  if (k == 0) {
    return Weight{};
  }
  const Weight inf = std::numeric_limits<Weight>::max();
  const int lim = 1 << k;
  std::vector<std::vector<Weight>> dis(lim, std::vector<Weight>(n, inf));
  for (int id = 0; id < k; id++) {
    dis[1 << id][ter[id]] = Weight{};
  }

  for (int msk = 1; msk < lim; msk++) {
    for (int sub = (msk - 1) & msk; sub > 0; sub = (sub - 1) & msk) {
      int rhs = msk ^ sub;
      if (sub > rhs) {
        continue;
      }
      for (int u = 0; u < n; u++) {
        if (dis[sub][u] == inf || dis[rhs][u] == inf) {
          continue;
        }
        dis[msk][u] = std::min(dis[msk][u], dis[sub][u] + dis[rhs][u]);
      }
    }

    using state = std::pair<Weight, int>;
    std::priority_queue<state, std::vector<state>, std::greater<>> q;
    for (int u = 0; u < n; u++) {
      if (dis[msk][u] != inf) {
        q.emplace(dis[msk][u], u);
      }
    }
    while (!q.empty()) {
      auto [cur, u] = q.top();
      q.pop();
      if (cur != dis[msk][u]) {
        continue;
      }
      for (auto [nxt, w] : g[u]) {
        if (cur <= inf - w && cur + w < dis[msk][nxt]) {
          dis[msk][nxt] = cur + w;
          q.emplace(dis[msk][nxt], nxt);
        }
      }
    }
  }
  return *std::min_element(dis.back().begin(), dis.back().end());
}

} // namespace noya
#ifndef NOYA_STEINER_TREE_HPP
#define NOYA_STEINER_TREE_HPP 1

/// @complexity Time: O(3^k V + 2^k E log V) for k ter.
/// 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 a;
  int b;
  Weight w;
};

template <class Weight> struct steiner_tree_result {
  Weight w;
  std::vector<int> eid;
};

/// @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>> &es,
                        std::vector<int> ter) {
  assert(n >= 0);
  struct adjacent_edge {
    int nxt;
    Weight w;
    int id;
  };
  std::vector<std::vector<adjacent_edge>> g(n);
  for (int id = 0; id < int(es.size()); id++) {
    auto [a, b, w] = es[id];
    assert(0 <= a && a < n && 0 <= b && b < n);
    assert(!(w < Weight{}));
    g[a].push_back({b, w, id});
    g[b].push_back({a, w, id});
  }
  for (int t : ter) {
    assert(0 <= t && t < n);
  }
  std::sort(ter.begin(), ter.end());
  ter.erase(std::unique(ter.begin(), ter.end()), ter.end());
  const int k = int(ter.size());
  assert(k < int(sizeof(unsigned) * 8));
  if (k == 0) {
    return {Weight{}, {}};
  }
  const Weight inf = std::numeric_limits<Weight>::max();
  const int lim = 1 << k;
  struct parent_record {
    int sub = 0;
    int pv = -1;
    int ei1 = -1;
  };
  std::vector<std::vector<Weight>> dis(lim, std::vector<Weight>(n, inf));
  std::vector<std::vector<parent_record>> fa(lim,
                                             std::vector<parent_record>(n));
  for (int id = 0; id < k; id++) {
    dis[1 << id][ter[id]] = Weight{};
  }

  for (int msk = 1; msk < lim; msk++) {
    for (int sub = (msk - 1) & msk; sub > 0; sub = (sub - 1) & msk) {
      int rhs = msk ^ sub;
      if (sub > rhs) {
        continue;
      }
      for (int u = 0; u < n; u++) {
        if (dis[sub][u] == inf || dis[rhs][u] == inf) {
          continue;
        }
        Weight can = dis[sub][u] + dis[rhs][u];
        if (can < dis[msk][u]) {
          dis[msk][u] = can;
          fa[msk][u] = {sub, -1, -1};
        }
      }
    }

    using state = std::pair<Weight, int>;
    std::priority_queue<state, std::vector<state>, std::greater<>> q;
    for (int u = 0; u < n; u++) {
      if (dis[msk][u] != inf) {
        q.emplace(dis[msk][u], u);
      }
    }
    while (!q.empty()) {
      auto [cur, u] = q.top();
      q.pop();
      if (cur != dis[msk][u]) {
        continue;
      }
      for (auto e : g[u]) {
        if (cur <= inf - e.w && cur + e.w < dis[msk][e.nxt]) {
          dis[msk][e.nxt] = cur + e.w;
          fa[msk][e.nxt] = {0, u, e.id};
          q.emplace(dis[msk][e.nxt], e.nxt);
        }
      }
    }
  }

  int rt = int(std::min_element(dis.back().begin(), dis.back().end()) -
               dis.back().begin());
  if (dis.back()[rt] == inf) {
    return {inf, {}};
  }
  std::vector<bool> vis(es.size());
  auto get = [&](auto &&self, int msk, int u) -> void {
    parent_record rec = fa[msk][u];
    if (rec.ei1 != -1) {
      self(self, msk, rec.pv);
      vis[rec.ei1] = true;
    } else if (rec.sub != 0) {
      self(self, rec.sub, u);
      self(self, msk ^ rec.sub, u);
    }
  };
  get(get, lim - 1, rt);
  std::vector<int> sel;
  Weight sw{};
  for (int id = 0; id < int(es.size()); id++) {
    if (vis[id]) {
      sel.push_back(id);
      sw += es[id].w;
    }
  }
  assert(sw == dis.back()[rt]);
  return {sw, std::move(sel)};
}

/// @brief Minimum weight of a connected subgraph containing all ter 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>>> &g,
                    std::vector<int> ter) {
  const int n = int(g.size());
  for (int t : ter) {
    assert(0 <= t && t < n);
  }
  for (int u = 0; u < n; u++) {
    for (auto [nxt, w] : g[u]) {
      assert(0 <= nxt && nxt < n);
      assert(!(w < Weight{}));
    }
  }
  std::sort(ter.begin(), ter.end());
  ter.erase(std::unique(ter.begin(), ter.end()), ter.end());
  const int k = int(ter.size());
  assert(k < int(sizeof(unsigned) * 8));
  if (k == 0) {
    return Weight{};
  }
  const Weight inf = std::numeric_limits<Weight>::max();
  const int lim = 1 << k;
  std::vector<std::vector<Weight>> dis(lim, std::vector<Weight>(n, inf));
  for (int id = 0; id < k; id++) {
    dis[1 << id][ter[id]] = Weight{};
  }

  for (int msk = 1; msk < lim; msk++) {
    for (int sub = (msk - 1) & msk; sub > 0; sub = (sub - 1) & msk) {
      int rhs = msk ^ sub;
      if (sub > rhs) {
        continue;
      }
      for (int u = 0; u < n; u++) {
        if (dis[sub][u] == inf || dis[rhs][u] == inf) {
          continue;
        }
        dis[msk][u] = std::min(dis[msk][u], dis[sub][u] + dis[rhs][u]);
      }
    }

    using state = std::pair<Weight, int>;
    std::priority_queue<state, std::vector<state>, std::greater<>> q;
    for (int u = 0; u < n; u++) {
      if (dis[msk][u] != inf) {
        q.emplace(dis[msk][u], u);
      }
    }
    while (!q.empty()) {
      auto [cur, u] = q.top();
      q.pop();
      if (cur != dis[msk][u]) {
        continue;
      }
      for (auto [nxt, w] : g[u]) {
        if (cur <= inf - w && cur + w < dis[msk][nxt]) {
          dis[msk][nxt] = cur + w;
          q.emplace(dis[msk][nxt], nxt);
        }
      }
    }
  }
  return *std::min_element(dis.back().begin(), dis.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 ter.
/// Space: O(2^k V).

namespace noya {

template <class Weight> struct steiner_tree_edge {
  int a;
  int b;
  Weight w;
};

template <class Weight> struct steiner_tree_result {
  Weight w;
  std::vector<int> eid;
};

/// @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>> &es,
                        std::vector<int> ter) {
  assert(n >= 0);
  struct adjacent_edge {
    int nxt;
    Weight w;
    int id;
  };
  std::vector<std::vector<adjacent_edge>> g(n);
  for (int id = 0; id < int(es.size()); id++) {
    auto [a, b, w] = es[id];
    assert(0 <= a && a < n && 0 <= b && b < n);
    assert(!(w < Weight{}));
    g[a].push_back({b, w, id});
    g[b].push_back({a, w, id});
  }
  for (int t : ter) {
    assert(0 <= t && t < n);
  }
  std::sort(ter.begin(), ter.end());
  ter.erase(std::unique(ter.begin(), ter.end()), ter.end());
  const int k = int(ter.size());
  assert(k < int(sizeof(unsigned) * 8));
  if (k == 0) {
    return {Weight{}, {}};
  }
  const Weight inf = std::numeric_limits<Weight>::max();
  const int lim = 1 << k;
  struct parent_record {
    int sub = 0;
    int pv = -1;
    int ei1 = -1;
  };
  std::vector<std::vector<Weight>> dis(lim, std::vector<Weight>(n, inf));
  std::vector<std::vector<parent_record>> fa(lim,
                                             std::vector<parent_record>(n));
  for (int id = 0; id < k; id++) {
    dis[1 << id][ter[id]] = Weight{};
  }

  for (int msk = 1; msk < lim; msk++) {
    for (int sub = (msk - 1) & msk; sub > 0; sub = (sub - 1) & msk) {
      int rhs = msk ^ sub;
      if (sub > rhs) {
        continue;
      }
      for (int u = 0; u < n; u++) {
        if (dis[sub][u] == inf || dis[rhs][u] == inf) {
          continue;
        }
        Weight can = dis[sub][u] + dis[rhs][u];
        if (can < dis[msk][u]) {
          dis[msk][u] = can;
          fa[msk][u] = {sub, -1, -1};
        }
      }
    }

    using state = std::pair<Weight, int>;
    std::priority_queue<state, std::vector<state>, std::greater<>> q;
    for (int u = 0; u < n; u++) {
      if (dis[msk][u] != inf) {
        q.emplace(dis[msk][u], u);
      }
    }
    while (!q.empty()) {
      auto [cur, u] = q.top();
      q.pop();
      if (cur != dis[msk][u]) {
        continue;
      }
      for (auto e : g[u]) {
        if (cur <= inf - e.w && cur + e.w < dis[msk][e.nxt]) {
          dis[msk][e.nxt] = cur + e.w;
          fa[msk][e.nxt] = {0, u, e.id};
          q.emplace(dis[msk][e.nxt], e.nxt);
        }
      }
    }
  }

  int rt = int(std::min_element(dis.back().begin(), dis.back().end()) -
               dis.back().begin());
  if (dis.back()[rt] == inf) {
    return {inf, {}};
  }
  std::vector<bool> vis(es.size());
  auto get = [&](auto &&self, int msk, int u) -> void {
    parent_record rec = fa[msk][u];
    if (rec.ei1 != -1) {
      self(self, msk, rec.pv);
      vis[rec.ei1] = true;
    } else if (rec.sub != 0) {
      self(self, rec.sub, u);
      self(self, msk ^ rec.sub, u);
    }
  };
  get(get, lim - 1, rt);
  std::vector<int> sel;
  Weight sw{};
  for (int id = 0; id < int(es.size()); id++) {
    if (vis[id]) {
      sel.push_back(id);
      sw += es[id].w;
    }
  }
  assert(sw == dis.back()[rt]);
  return {sw, std::move(sel)};
}

/// @brief Minimum weight of a connected subgraph containing all ter 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>>> &g,
                    std::vector<int> ter) {
  const int n = int(g.size());
  for (int t : ter) {
    assert(0 <= t && t < n);
  }
  for (int u = 0; u < n; u++) {
    for (auto [nxt, w] : g[u]) {
      assert(0 <= nxt && nxt < n);
      assert(!(w < Weight{}));
    }
  }
  std::sort(ter.begin(), ter.end());
  ter.erase(std::unique(ter.begin(), ter.end()), ter.end());
  const int k = int(ter.size());
  assert(k < int(sizeof(unsigned) * 8));
  if (k == 0) {
    return Weight{};
  }
  const Weight inf = std::numeric_limits<Weight>::max();
  const int lim = 1 << k;
  std::vector<std::vector<Weight>> dis(lim, std::vector<Weight>(n, inf));
  for (int id = 0; id < k; id++) {
    dis[1 << id][ter[id]] = Weight{};
  }

  for (int msk = 1; msk < lim; msk++) {
    for (int sub = (msk - 1) & msk; sub > 0; sub = (sub - 1) & msk) {
      int rhs = msk ^ sub;
      if (sub > rhs) {
        continue;
      }
      for (int u = 0; u < n; u++) {
        if (dis[sub][u] == inf || dis[rhs][u] == inf) {
          continue;
        }
        dis[msk][u] = std::min(dis[msk][u], dis[sub][u] + dis[rhs][u]);
      }
    }

    using state = std::pair<Weight, int>;
    std::priority_queue<state, std::vector<state>, std::greater<>> q;
    for (int u = 0; u < n; u++) {
      if (dis[msk][u] != inf) {
        q.emplace(dis[msk][u], u);
      }
    }
    while (!q.empty()) {
      auto [cur, u] = q.top();
      q.pop();
      if (cur != dis[msk][u]) {
        continue;
      }
      for (auto [nxt, w] : g[u]) {
        if (cur <= inf - w && cur + w < dis[msk][nxt]) {
          dis[msk][nxt] = cur + w;
          q.emplace(dis[msk][nxt], nxt);
        }
      }
    }
  }
  return *std::min_element(dis.back().begin(), dis.back().end());
}

} // namespace noya