Skip to content

steiner_tree.hpp

SECTIONGraph INCLUDEnoya/graph/steiner_tree.hpp

用子集 DP 与 Dijkstra 求非负权无向图中连接少量终端的最小代价,并可恢复原边编号;固定一个终端使状态数减半,先检查连通性,安全处理超范围代价。

Complexity: Time: \(O(t \log (t + 1) + n + m + 3^{r} n + 2^{r} (n + m) \log (n + m + 2))\), \(r = \max (k - 1 , 0)\) for \(t\) input and \(k\) distinct terminals. Space: \(O(t + 2^{r} n + n + m)\), including witness parents.

AC 记录:minimum_steiner_tree。

跳到代码 · GitHub ↗

Implementation

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

/// @complexity Time: O(t log(t+1)+n+m+3^r n+2^r(n+m) log(n+m+2)), r=max(k-1,0)
/// for t input and k distinct terminals. Space: O(t+2^r n+n+m), including witness parents.

#include <algorithm>
#include <cassert>
#include <cstdint>
#include <functional>
#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.
/// Require valid vertices, finite nonnegative weights, and edge IDs fitting int.
/// Duplicate terminals, loops and parallel edges are allowed. No/one terminal
/// costs zero. max(Weight) is the unreachable/unrepresentable-cost sentinel;
/// finite answers must be strictly smaller. Inputs are unchanged; tied optimal
/// witnesses may differ. Selected IDs are unique and returned in increasing order.
/// At most numeric_limits<unsigned>::digits distinct terminals are supported;
/// the exponential table must fit memory. Floating arithmetic is approximate:
/// witness cost sums selected edges in ID order, which can round differently
/// from the cost-only DP.
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 && es.size() <= std::size_t(std::numeric_limits<int>::max()));
  for (auto [a, b, w] : es) {
    assert(0 <= a && a < n && 0 <= b && b < n);
    assert(!(w < Weight{}) && w <= std::numeric_limits<Weight>::max());
  }
  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());
  if (ter.size() <= 1) return {Weight{}, {}};
  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];
    g[a].push_back({b, w, id});
    g[b].push_back({a, w, id});
  }
  int rt = ter.back();
  ter.pop_back();
  const int k = int(ter.size());
  assert(k < std::numeric_limits<unsigned>::digits);
  const unsigned lim = 1u << k;
  const Weight inf = std::numeric_limits<Weight>::max();
  // Reject disconnected terminal sets before allocating the exponential table.
  std::vector<unsigned char> reachable(n);
  std::vector<int> queue{rt};
  reachable[rt] = true;
  for (std::size_t i = 0; i < queue.size(); ++i)
    for (const auto &e : g[queue[i]]) if (!reachable[e.nxt]) {
      reachable[e.nxt] = true;
      queue.push_back(e.nxt);
    }
  for (int t : ter) if (!reachable[t]) return {inf, {}};
  struct parent_record {
    unsigned 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[1u << id][ter[id]] = Weight{};
  }

  for (unsigned msk = 1; msk < lim; msk++) {
    for (unsigned sub = (msk - 1) & msk; sub > 0; sub = (sub - 1) & msk) {
      unsigned rhs = msk ^ sub;
      if (sub > rhs) {
        continue;
      }
      for (int u = 0; u < n; u++) {
        if (dis[sub][u] == inf || dis[rhs][u] == inf ||
            dis[sub][u] > inf - dis[rhs][u]) {
          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::vector<state> seeds;
    seeds.reserve(n);
    for (int u = 0; u < n; u++) {
      if (dis[msk][u] != inf) seeds.emplace_back(dis[msk][u], u);
    }
    std::priority_queue<state, std::vector<state>, std::greater<>>
        q(std::greater<>{}, std::move(seeds));
    while (!q.empty()) {
      auto [cur, u] = q.top();
      q.pop();
      if (cur != dis[msk][u]) {
        continue;
      }
      if (msk == lim - 1 && u == rt) break;
      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);
        }
      }
    }
  }

  if (dis.back()[rt] == inf) {
    return {inf, {}};
  }
  std::vector<bool> vis(es.size());
  auto get = [&](auto &&self, unsigned 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;
    }
  }
  if constexpr (std::numeric_limits<Weight>::is_integer)
    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. Each edge must appear in both
/// adjacency lists. The same terminal and sentinel contracts apply as above.
template <class Weight>
Weight steiner_tree(const std::vector<std::vector<std::pair<int, Weight>>> &g,
                    std::vector<int> ter) {
  assert(g.size() <= std::size_t(std::numeric_limits<int>::max()));
  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{}) && w <= std::numeric_limits<Weight>::max());
    }
  }
  std::sort(ter.begin(), ter.end());
  ter.erase(std::unique(ter.begin(), ter.end()), ter.end());
  if (ter.size() <= 1) return Weight{};
  int rt = ter.back();
  ter.pop_back();
  const int k = int(ter.size());
  assert(k < std::numeric_limits<unsigned>::digits);
  const unsigned lim = 1u << k;
  const Weight inf = std::numeric_limits<Weight>::max();
  std::vector<unsigned char> reachable(n);
  std::vector<int> queue{rt};
  reachable[rt] = true;
  for (std::size_t i = 0; i < queue.size(); ++i)
    for (const auto &[v, w] : g[queue[i]]) if (!reachable[v]) {
      reachable[v] = true;
      queue.push_back(v);
    }
  for (int t : ter) if (!reachable[t]) return inf;
  std::vector<std::vector<Weight>> dis(lim, std::vector<Weight>(n, inf));
  for (int id = 0; id < k; id++) {
    dis[1u << id][ter[id]] = Weight{};
  }

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

    using state = std::pair<Weight, int>;
    std::vector<state> seeds;
    seeds.reserve(n);
    for (int u = 0; u < n; u++) {
      if (dis[msk][u] != inf) seeds.emplace_back(dis[msk][u], u);
    }
    std::priority_queue<state, std::vector<state>, std::greater<>>
        q(std::greater<>{}, std::move(seeds));
    while (!q.empty()) {
      auto [cur, u] = q.top();
      q.pop();
      if (cur != dis[msk][u]) {
        continue;
      }
      if (msk == lim - 1 && u == rt) break;
      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 dis.back()[rt];
}

} // namespace noya

 // NOYA_STEINER_TREE_HPP
#ifndef NOYA_STEINER_TREE_HPP
#define NOYA_STEINER_TREE_HPP 1

/// @complexity Time: O(t log(t+1)+n+m+3^r n+2^r(n+m) log(n+m+2)), r=max(k-1,0)
/// for t input and k distinct terminals. Space: O(t+2^r n+n+m), including witness parents.

#include <algorithm>
#include <cassert>
#include <cstdint>
#include <functional>
#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.
/// Require valid vertices, finite nonnegative weights, and edge IDs fitting int.
/// Duplicate terminals, loops and parallel edges are allowed. No/one terminal
/// costs zero. max(Weight) is the unreachable/unrepresentable-cost sentinel;
/// finite answers must be strictly smaller. Inputs are unchanged; tied optimal
/// witnesses may differ. Selected IDs are unique and returned in increasing order.
/// At most numeric_limits<unsigned>::digits distinct terminals are supported;
/// the exponential table must fit memory. Floating arithmetic is approximate:
/// witness cost sums selected edges in ID order, which can round differently
/// from the cost-only DP.
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 && es.size() <= std::size_t(std::numeric_limits<int>::max()));
  for (auto [a, b, w] : es) {
    assert(0 <= a && a < n && 0 <= b && b < n);
    assert(!(w < Weight{}) && w <= std::numeric_limits<Weight>::max());
  }
  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());
  if (ter.size() <= 1) return {Weight{}, {}};
  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];
    g[a].push_back({b, w, id});
    g[b].push_back({a, w, id});
  }
  int rt = ter.back();
  ter.pop_back();
  const int k = int(ter.size());
  assert(k < std::numeric_limits<unsigned>::digits);
  const unsigned lim = 1u << k;
  const Weight inf = std::numeric_limits<Weight>::max();
  // Reject disconnected terminal sets before allocating the exponential table.
  std::vector<unsigned char> reachable(n);
  std::vector<int> queue{rt};
  reachable[rt] = true;
  for (std::size_t i = 0; i < queue.size(); ++i)
    for (const auto &e : g[queue[i]]) if (!reachable[e.nxt]) {
      reachable[e.nxt] = true;
      queue.push_back(e.nxt);
    }
  for (int t : ter) if (!reachable[t]) return {inf, {}};
  struct parent_record {
    unsigned 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[1u << id][ter[id]] = Weight{};
  }

  for (unsigned msk = 1; msk < lim; msk++) {
    for (unsigned sub = (msk - 1) & msk; sub > 0; sub = (sub - 1) & msk) {
      unsigned rhs = msk ^ sub;
      if (sub > rhs) {
        continue;
      }
      for (int u = 0; u < n; u++) {
        if (dis[sub][u] == inf || dis[rhs][u] == inf ||
            dis[sub][u] > inf - dis[rhs][u]) {
          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::vector<state> seeds;
    seeds.reserve(n);
    for (int u = 0; u < n; u++) {
      if (dis[msk][u] != inf) seeds.emplace_back(dis[msk][u], u);
    }
    std::priority_queue<state, std::vector<state>, std::greater<>>
        q(std::greater<>{}, std::move(seeds));
    while (!q.empty()) {
      auto [cur, u] = q.top();
      q.pop();
      if (cur != dis[msk][u]) {
        continue;
      }
      if (msk == lim - 1 && u == rt) break;
      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);
        }
      }
    }
  }

  if (dis.back()[rt] == inf) {
    return {inf, {}};
  }
  std::vector<bool> vis(es.size());
  auto get = [&](auto &&self, unsigned 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;
    }
  }
  if constexpr (std::numeric_limits<Weight>::is_integer)
    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. Each edge must appear in both
/// adjacency lists. The same terminal and sentinel contracts apply as above.
template <class Weight>
Weight steiner_tree(const std::vector<std::vector<std::pair<int, Weight>>> &g,
                    std::vector<int> ter) {
  assert(g.size() <= std::size_t(std::numeric_limits<int>::max()));
  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{}) && w <= std::numeric_limits<Weight>::max());
    }
  }
  std::sort(ter.begin(), ter.end());
  ter.erase(std::unique(ter.begin(), ter.end()), ter.end());
  if (ter.size() <= 1) return Weight{};
  int rt = ter.back();
  ter.pop_back();
  const int k = int(ter.size());
  assert(k < std::numeric_limits<unsigned>::digits);
  const unsigned lim = 1u << k;
  const Weight inf = std::numeric_limits<Weight>::max();
  std::vector<unsigned char> reachable(n);
  std::vector<int> queue{rt};
  reachable[rt] = true;
  for (std::size_t i = 0; i < queue.size(); ++i)
    for (const auto &[v, w] : g[queue[i]]) if (!reachable[v]) {
      reachable[v] = true;
      queue.push_back(v);
    }
  for (int t : ter) if (!reachable[t]) return inf;
  std::vector<std::vector<Weight>> dis(lim, std::vector<Weight>(n, inf));
  for (int id = 0; id < k; id++) {
    dis[1u << id][ter[id]] = Weight{};
  }

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

    using state = std::pair<Weight, int>;
    std::vector<state> seeds;
    seeds.reserve(n);
    for (int u = 0; u < n; u++) {
      if (dis[msk][u] != inf) seeds.emplace_back(dis[msk][u], u);
    }
    std::priority_queue<state, std::vector<state>, std::greater<>>
        q(std::greater<>{}, std::move(seeds));
    while (!q.empty()) {
      auto [cur, u] = q.top();
      q.pop();
      if (cur != dis[msk][u]) {
        continue;
      }
      if (msk == lim - 1 && u == rt) break;
      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 dis.back()[rt];
}

} // namespace noya

#endif // NOYA_STEINER_TREE_HPP
/// @complexity Time: O(t log(t+1)+n+m+3^r n+2^r(n+m) log(n+m+2)), r=max(k-1,0)
/// for t input and k distinct terminals. Space: O(t+2^r n+n+m), including witness parents.

#include <algorithm>
#include <cassert>
#include <cstdint>
#include <functional>
#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.
/// Require valid vertices, finite nonnegative weights, and edge IDs fitting int.
/// Duplicate terminals, loops and parallel edges are allowed. No/one terminal
/// costs zero. max(Weight) is the unreachable/unrepresentable-cost sentinel;
/// finite answers must be strictly smaller. Inputs are unchanged; tied optimal
/// witnesses may differ. Selected IDs are unique and returned in increasing order.
/// At most numeric_limits<unsigned>::digits distinct terminals are supported;
/// the exponential table must fit memory. Floating arithmetic is approximate:
/// witness cost sums selected edges in ID order, which can round differently
/// from the cost-only DP.
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 && es.size() <= std::size_t(std::numeric_limits<int>::max()));
  for (auto [a, b, w] : es) {
    assert(0 <= a && a < n && 0 <= b && b < n);
    assert(!(w < Weight{}) && w <= std::numeric_limits<Weight>::max());
  }
  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());
  if (ter.size() <= 1) return {Weight{}, {}};
  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];
    g[a].push_back({b, w, id});
    g[b].push_back({a, w, id});
  }
  int rt = ter.back();
  ter.pop_back();
  const int k = int(ter.size());
  assert(k < std::numeric_limits<unsigned>::digits);
  const unsigned lim = 1u << k;
  const Weight inf = std::numeric_limits<Weight>::max();
  // Reject disconnected terminal sets before allocating the exponential table.
  std::vector<unsigned char> reachable(n);
  std::vector<int> queue{rt};
  reachable[rt] = true;
  for (std::size_t i = 0; i < queue.size(); ++i)
    for (const auto &e : g[queue[i]]) if (!reachable[e.nxt]) {
      reachable[e.nxt] = true;
      queue.push_back(e.nxt);
    }
  for (int t : ter) if (!reachable[t]) return {inf, {}};
  struct parent_record {
    unsigned 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[1u << id][ter[id]] = Weight{};
  }

  for (unsigned msk = 1; msk < lim; msk++) {
    for (unsigned sub = (msk - 1) & msk; sub > 0; sub = (sub - 1) & msk) {
      unsigned rhs = msk ^ sub;
      if (sub > rhs) {
        continue;
      }
      for (int u = 0; u < n; u++) {
        if (dis[sub][u] == inf || dis[rhs][u] == inf ||
            dis[sub][u] > inf - dis[rhs][u]) {
          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::vector<state> seeds;
    seeds.reserve(n);
    for (int u = 0; u < n; u++) {
      if (dis[msk][u] != inf) seeds.emplace_back(dis[msk][u], u);
    }
    std::priority_queue<state, std::vector<state>, std::greater<>>
        q(std::greater<>{}, std::move(seeds));
    while (!q.empty()) {
      auto [cur, u] = q.top();
      q.pop();
      if (cur != dis[msk][u]) {
        continue;
      }
      if (msk == lim - 1 && u == rt) break;
      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);
        }
      }
    }
  }

  if (dis.back()[rt] == inf) {
    return {inf, {}};
  }
  std::vector<bool> vis(es.size());
  auto get = [&](auto &&self, unsigned 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;
    }
  }
  if constexpr (std::numeric_limits<Weight>::is_integer)
    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. Each edge must appear in both
/// adjacency lists. The same terminal and sentinel contracts apply as above.
template <class Weight>
Weight steiner_tree(const std::vector<std::vector<std::pair<int, Weight>>> &g,
                    std::vector<int> ter) {
  assert(g.size() <= std::size_t(std::numeric_limits<int>::max()));
  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{}) && w <= std::numeric_limits<Weight>::max());
    }
  }
  std::sort(ter.begin(), ter.end());
  ter.erase(std::unique(ter.begin(), ter.end()), ter.end());
  if (ter.size() <= 1) return Weight{};
  int rt = ter.back();
  ter.pop_back();
  const int k = int(ter.size());
  assert(k < std::numeric_limits<unsigned>::digits);
  const unsigned lim = 1u << k;
  const Weight inf = std::numeric_limits<Weight>::max();
  std::vector<unsigned char> reachable(n);
  std::vector<int> queue{rt};
  reachable[rt] = true;
  for (std::size_t i = 0; i < queue.size(); ++i)
    for (const auto &[v, w] : g[queue[i]]) if (!reachable[v]) {
      reachable[v] = true;
      queue.push_back(v);
    }
  for (int t : ter) if (!reachable[t]) return inf;
  std::vector<std::vector<Weight>> dis(lim, std::vector<Weight>(n, inf));
  for (int id = 0; id < k; id++) {
    dis[1u << id][ter[id]] = Weight{};
  }

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

    using state = std::pair<Weight, int>;
    std::vector<state> seeds;
    seeds.reserve(n);
    for (int u = 0; u < n; u++) {
      if (dis[msk][u] != inf) seeds.emplace_back(dis[msk][u], u);
    }
    std::priority_queue<state, std::vector<state>, std::greater<>>
        q(std::greater<>{}, std::move(seeds));
    while (!q.empty()) {
      auto [cur, u] = q.top();
      q.pop();
      if (cur != dis[msk][u]) {
        continue;
      }
      if (msk == lim - 1 && u == rt) break;
      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 dis.back()[rt];
}

} // namespace noya

 // NOYA_STEINER_TREE_HPP