Skip to content

minimum_diameter_spanning_tree.hpp

SECTIONGraph INCLUDEnoya/minimum_diameter_spanning_tree.hpp

在带权无向图中求直径最小的生成树,并构造对应树边。

Complexity: Time: O(V(E + V) log V + VE + V^2 log V). Space: O(V^2 + V + E).

AC 记录:minimum_diameter_spanning_tree

跳到代码 · GitHub ↗

Implementation

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

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

#include <algorithm>
#include <cassert>
#include <functional>
#include <limits>
#include <queue>
#include <tuple>
#include <utility>
#include <vector>

namespace noya {

template <class Weight> struct minimum_diameter_spanning_tree_result {
  Weight dia{};
  std::vector<int> eid;
};

/// @brief Construct a minimum-diameter spanning tree of a connected graph.
/// The center of an optimal tree can be placed at a vertex or inside one
/// graph edge. All-pairs shortest paths give every vertex eccentricity. For
/// an edge center, sorting vertices by distance from one endpoint makes the
/// maximum of the two endpoint-distance envelopes change monotonically, so
/// one sweep tests its best split point. Doubling edge weights keeps every
/// half-integral center exact. Finally, a shortest-path tree rooted at the
/// best absolute center realizes the selected minimum diameter.
template <class Weight>
minimum_diameter_spanning_tree_result<Weight> minimum_diameter_spanning_tree(
    int n, const std::vector<std::tuple<int, int, Weight>> &es) {
  assert(n > 0);
  struct adjacent_edge {
    int to;
    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);
    assert(0 <= b && b < n);
    assert(w >= Weight{});
    w *= Weight(2);
    g[a].push_back({b, w, id});
    g[b].push_back({a, w, id});
  }

  const Weight inf = std::numeric_limits<Weight>::max() / 4;
  struct shortest_path_result {
    std::vector<Weight> dis;
    std::vector<int> pre;
    std::vector<int> pe;
  };
  auto sp = [&](int s, const std::vector<std::vector<adjacent_edge>> &G) {
    int siz = int(G.size());
    shortest_path_result res{std::vector<Weight>(siz, inf),
                             std::vector<int>(siz, -1),
                             std::vector<int>(siz, -1)};
    using state = std::pair<Weight, int>;
    std::priority_queue<state, std::vector<state>, std::greater<state>> q;
    res.dis[s] = Weight{};
    q.emplace(Weight{}, s);
    while (!q.empty()) {
      auto [dis, u] = q.top();
      q.pop();
      if (dis != res.dis[u]) {
        continue;
      }
      for (auto [to, w, id] : G[u]) {
        Weight can = dis + w;
        if (can < res.dis[to]) {
          res.dis[to] = can;
          res.pre[to] = u;
          res.pe[to] = id;
          q.emplace(can, to);
        }
      }
    }
    return res;
  };

  std::vector<std::vector<Weight>> dis(n, std::vector<Weight>(n));
  for (int s = 0; s < n; s++) {
    dis[s] = sp(s, g).dis;
    for (Weight val : dis[s]) {
      assert(val != inf);
    }
  }

  std::vector<std::vector<int>> ord(n, std::vector<int>(n));
  Weight bvs = inf;
  int bv = 0;
  for (int u = 0; u < n; u++) {
    for (int rhs = 0; rhs < n; rhs++) {
      ord[u][rhs] = rhs;
    }
    std::stable_sort(ord[u].begin(), ord[u].end(),
                     [&](int a, int b) { return dis[u][a] > dis[u][b]; });
    Weight va1 = dis[u][ord[u][0]] * Weight(2);
    if (va1 < bvs) {
      bvs = va1;
      bv = u;
    }
  }

  Weight bes = inf;
  int be = -1;
  int bu = -1;
  int bw = -1;
  Weight du{};
  Weight dv{};
  for (int id = 0; id < int(es.size()); id++) {
    auto [a, b, w0] = es[id];
    if (a == b) {
      continue;
    }
    Weight w = w0 * Weight(2);
    int prv = ord[a][0];
    for (int u : ord[a]) {
      if (dis[b][u] > dis[b][prv]) {
        Weight va1 = dis[b][prv] + dis[a][u] + w;
        if (va1 < bes) {
          bes = va1;
          be = id;
          bu = a;
          bw = b;
          du = va1 / Weight(2) - dis[a][u];
          dv = w - du;
        }
        prv = u;
      }
    }
  }

  minimum_diameter_spanning_tree_result<Weight> res;
  if (bes < bvs) {
    int o = n;
    auto aug = g;
    aug.emplace_back();
    aug[o].push_back({bu, du, -1});
    aug[o].push_back({bw, dv, -1});
    auto tr = sp(o, aug);
    res.dia = bes / Weight(2);
    res.eid.reserve(n - 1);
    for (int u = 0; u < n; u++) {
      if (u != bu && u != bw) {
        assert(tr.pe[u] >= 0);
        res.eid.push_back(tr.pe[u]);
      }
    }
    res.eid.push_back(be);
  } else {
    auto tr = sp(bv, g);
    res.dia = bvs / Weight(2);
    res.eid.reserve(n - 1);
    for (int u = 0; u < n; u++) {
      if (u != bv) {
        assert(tr.pe[u] >= 0);
        res.eid.push_back(tr.pe[u]);
      }
    }
  }
  return res;
}

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

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

#include <algorithm>
#include <cassert>
#include <functional>
#include <limits>
#include <queue>
#include <tuple>
#include <utility>
#include <vector>

namespace noya {

template <class Weight> struct minimum_diameter_spanning_tree_result {
  Weight dia{};
  std::vector<int> eid;
};

/// @brief Construct a minimum-diameter spanning tree of a connected graph.
/// The center of an optimal tree can be placed at a vertex or inside one
/// graph edge. All-pairs shortest paths give every vertex eccentricity. For
/// an edge center, sorting vertices by distance from one endpoint makes the
/// maximum of the two endpoint-distance envelopes change monotonically, so
/// one sweep tests its best split point. Doubling edge weights keeps every
/// half-integral center exact. Finally, a shortest-path tree rooted at the
/// best absolute center realizes the selected minimum diameter.
template <class Weight>
minimum_diameter_spanning_tree_result<Weight> minimum_diameter_spanning_tree(
    int n, const std::vector<std::tuple<int, int, Weight>> &es) {
  assert(n > 0);
  struct adjacent_edge {
    int to;
    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);
    assert(0 <= b && b < n);
    assert(w >= Weight{});
    w *= Weight(2);
    g[a].push_back({b, w, id});
    g[b].push_back({a, w, id});
  }

  const Weight inf = std::numeric_limits<Weight>::max() / 4;
  struct shortest_path_result {
    std::vector<Weight> dis;
    std::vector<int> pre;
    std::vector<int> pe;
  };
  auto sp = [&](int s, const std::vector<std::vector<adjacent_edge>> &G) {
    int siz = int(G.size());
    shortest_path_result res{std::vector<Weight>(siz, inf),
                             std::vector<int>(siz, -1),
                             std::vector<int>(siz, -1)};
    using state = std::pair<Weight, int>;
    std::priority_queue<state, std::vector<state>, std::greater<state>> q;
    res.dis[s] = Weight{};
    q.emplace(Weight{}, s);
    while (!q.empty()) {
      auto [dis, u] = q.top();
      q.pop();
      if (dis != res.dis[u]) {
        continue;
      }
      for (auto [to, w, id] : G[u]) {
        Weight can = dis + w;
        if (can < res.dis[to]) {
          res.dis[to] = can;
          res.pre[to] = u;
          res.pe[to] = id;
          q.emplace(can, to);
        }
      }
    }
    return res;
  };

  std::vector<std::vector<Weight>> dis(n, std::vector<Weight>(n));
  for (int s = 0; s < n; s++) {
    dis[s] = sp(s, g).dis;
    for (Weight val : dis[s]) {
      assert(val != inf);
    }
  }

  std::vector<std::vector<int>> ord(n, std::vector<int>(n));
  Weight bvs = inf;
  int bv = 0;
  for (int u = 0; u < n; u++) {
    for (int rhs = 0; rhs < n; rhs++) {
      ord[u][rhs] = rhs;
    }
    std::stable_sort(ord[u].begin(), ord[u].end(),
                     [&](int a, int b) { return dis[u][a] > dis[u][b]; });
    Weight va1 = dis[u][ord[u][0]] * Weight(2);
    if (va1 < bvs) {
      bvs = va1;
      bv = u;
    }
  }

  Weight bes = inf;
  int be = -1;
  int bu = -1;
  int bw = -1;
  Weight du{};
  Weight dv{};
  for (int id = 0; id < int(es.size()); id++) {
    auto [a, b, w0] = es[id];
    if (a == b) {
      continue;
    }
    Weight w = w0 * Weight(2);
    int prv = ord[a][0];
    for (int u : ord[a]) {
      if (dis[b][u] > dis[b][prv]) {
        Weight va1 = dis[b][prv] + dis[a][u] + w;
        if (va1 < bes) {
          bes = va1;
          be = id;
          bu = a;
          bw = b;
          du = va1 / Weight(2) - dis[a][u];
          dv = w - du;
        }
        prv = u;
      }
    }
  }

  minimum_diameter_spanning_tree_result<Weight> res;
  if (bes < bvs) {
    int o = n;
    auto aug = g;
    aug.emplace_back();
    aug[o].push_back({bu, du, -1});
    aug[o].push_back({bw, dv, -1});
    auto tr = sp(o, aug);
    res.dia = bes / Weight(2);
    res.eid.reserve(n - 1);
    for (int u = 0; u < n; u++) {
      if (u != bu && u != bw) {
        assert(tr.pe[u] >= 0);
        res.eid.push_back(tr.pe[u]);
      }
    }
    res.eid.push_back(be);
  } else {
    auto tr = sp(bv, g);
    res.dia = bvs / Weight(2);
    res.eid.reserve(n - 1);
    for (int u = 0; u < n; u++) {
      if (u != bv) {
        assert(tr.pe[u] >= 0);
        res.eid.push_back(tr.pe[u]);
      }
    }
  }
  return res;
}

} // namespace noya

#endif // NOYA_MINIMUM_DIAMETER_SPANNING_TREE_HPP
#include <algorithm>
#include <cassert>
#include <functional>
#include <limits>
#include <queue>
#include <tuple>
#include <utility>
#include <vector>

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

namespace noya {

template <class Weight> struct minimum_diameter_spanning_tree_result {
  Weight dia{};
  std::vector<int> eid;
};

/// @brief Construct a minimum-diameter spanning tree of a connected graph.
/// The center of an optimal tree can be placed at a vertex or inside one
/// graph edge. All-pairs shortest paths give every vertex eccentricity. For
/// an edge center, sorting vertices by distance from one endpoint makes the
/// maximum of the two endpoint-distance envelopes change monotonically, so
/// one sweep tests its best split point. Doubling edge weights keeps every
/// half-integral center exact. Finally, a shortest-path tree rooted at the
/// best absolute center realizes the selected minimum diameter.
template <class Weight>
minimum_diameter_spanning_tree_result<Weight> minimum_diameter_spanning_tree(
    int n, const std::vector<std::tuple<int, int, Weight>> &es) {
  assert(n > 0);
  struct adjacent_edge {
    int to;
    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);
    assert(0 <= b && b < n);
    assert(w >= Weight{});
    w *= Weight(2);
    g[a].push_back({b, w, id});
    g[b].push_back({a, w, id});
  }

  const Weight inf = std::numeric_limits<Weight>::max() / 4;
  struct shortest_path_result {
    std::vector<Weight> dis;
    std::vector<int> pre;
    std::vector<int> pe;
  };
  auto sp = [&](int s, const std::vector<std::vector<adjacent_edge>> &G) {
    int siz = int(G.size());
    shortest_path_result res{std::vector<Weight>(siz, inf),
                             std::vector<int>(siz, -1),
                             std::vector<int>(siz, -1)};
    using state = std::pair<Weight, int>;
    std::priority_queue<state, std::vector<state>, std::greater<state>> q;
    res.dis[s] = Weight{};
    q.emplace(Weight{}, s);
    while (!q.empty()) {
      auto [dis, u] = q.top();
      q.pop();
      if (dis != res.dis[u]) {
        continue;
      }
      for (auto [to, w, id] : G[u]) {
        Weight can = dis + w;
        if (can < res.dis[to]) {
          res.dis[to] = can;
          res.pre[to] = u;
          res.pe[to] = id;
          q.emplace(can, to);
        }
      }
    }
    return res;
  };

  std::vector<std::vector<Weight>> dis(n, std::vector<Weight>(n));
  for (int s = 0; s < n; s++) {
    dis[s] = sp(s, g).dis;
    for (Weight val : dis[s]) {
      assert(val != inf);
    }
  }

  std::vector<std::vector<int>> ord(n, std::vector<int>(n));
  Weight bvs = inf;
  int bv = 0;
  for (int u = 0; u < n; u++) {
    for (int rhs = 0; rhs < n; rhs++) {
      ord[u][rhs] = rhs;
    }
    std::stable_sort(ord[u].begin(), ord[u].end(),
                     [&](int a, int b) { return dis[u][a] > dis[u][b]; });
    Weight va1 = dis[u][ord[u][0]] * Weight(2);
    if (va1 < bvs) {
      bvs = va1;
      bv = u;
    }
  }

  Weight bes = inf;
  int be = -1;
  int bu = -1;
  int bw = -1;
  Weight du{};
  Weight dv{};
  for (int id = 0; id < int(es.size()); id++) {
    auto [a, b, w0] = es[id];
    if (a == b) {
      continue;
    }
    Weight w = w0 * Weight(2);
    int prv = ord[a][0];
    for (int u : ord[a]) {
      if (dis[b][u] > dis[b][prv]) {
        Weight va1 = dis[b][prv] + dis[a][u] + w;
        if (va1 < bes) {
          bes = va1;
          be = id;
          bu = a;
          bw = b;
          du = va1 / Weight(2) - dis[a][u];
          dv = w - du;
        }
        prv = u;
      }
    }
  }

  minimum_diameter_spanning_tree_result<Weight> res;
  if (bes < bvs) {
    int o = n;
    auto aug = g;
    aug.emplace_back();
    aug[o].push_back({bu, du, -1});
    aug[o].push_back({bw, dv, -1});
    auto tr = sp(o, aug);
    res.dia = bes / Weight(2);
    res.eid.reserve(n - 1);
    for (int u = 0; u < n; u++) {
      if (u != bu && u != bw) {
        assert(tr.pe[u] >= 0);
        res.eid.push_back(tr.pe[u]);
      }
    }
    res.eid.push_back(be);
  } else {
    auto tr = sp(bv, g);
    res.dia = bvs / Weight(2);
    res.eid.reserve(n - 1);
    for (int u = 0; u < n; u++) {
      if (u != bv) {
        assert(tr.pe[u] >= 0);
        res.eid.push_back(tr.pe[u]);
      }
    }
  }
  return res;
}

} // namespace noya