競技プログラミング用 C++ ライブラリ
graph/kruskal.hpp · GitHub で見る
共通ページ
重み付き無向グラフの最小全域木を求めます。非連結グラフでは、各連結成分の最小全域木を合わせた最小全域森を返します。
#include "lib/graph/kruskal.hpp"
Kruskal<> graph(4);
graph.add_edge(0, 1, 3); // 辺 ID 0
graph.add_edge(1, 2, 1); // 辺 ID 1
graph.add_edge(0, 2, 2); // 辺 ID 2
graph.add_edge(2, 3, 4); // 辺 ID 3
auto result = graph.solve();
assert(result.connected());
assert(result.cost == 7);
assert((result.edges == std::vector<int>{1, 2, 3}));
for (int id : result.edges) {
auto [u, v, cost] = graph.get_edge(id);
}
| 操作・メンバー | 内容・返り値 | 計算量 |
|---|---|---|
Kruskal<Cost,Sum>(n = 0) |
頂点数を指定。既定値は Cost=int64_t、Sum=Cost
|
\(O(1)\) |
add_edge(u,v,cost) |
無向辺を追加し、0 始まりの辺 ID を返す | 償却 \(O(1)\) |
solve() |
最小全域森の Result を返す |
\(O(n+m\log m)\) |
result.cost |
採用した辺の重みの総和(Sum) |
\(O(1)\) |
result.edges |
採用した辺 ID の vector<int>
|
\(O(1)\) |
result.components |
元のグラフの連結成分数。孤立点も数える | \(O(1)\) |
result.connected() |
連結か。空グラフも true とする |
\(O(1)\) |
get_edge(id) |
辺を Edge{from,to,cost} として取得 |
\(O(1)\) |
edges() |
入力順の辺一覧を const vector<Edge>& で取得 |
\(O(1)\) |
size() / edge_count()
|
頂点数/辺数 | \(O(1)\) |
reserve(m) |
辺 m 本分の領域を予約 |
\(O(m)\) |
clear() |
全ての辺を削除。頂点数は維持し、辺 ID は 0 から再開 | \(O(m)\) |
頂点番号は [0,n) です。負の重み・多重辺・自己ループ・孤立点に対応します。自己ループは採用しません。採用する辺数は n-result.components です。空グラフでは総和 0、辺一覧は空、成分数 0 を返します。
辺は重みの昇順で採用し、同じ重みなら辺 ID が小さいものを優先します。solve() は入力を変更せず、辺の追加後にも再実行できます。返された結果はグラフを参照しません。
Cost は各辺の重みの型、Sum は総和の型です。どちらも整数型、またはどちらも浮動小数点型とし、Sum は Cost の全ての値を表現できる型を指定します。例えば Kruskal<uint32_t,uint64_t> は辺を 32 bit、総和を 64 bit で扱います。128 bit の総和には Kruskal<int64_t,__int128_t> を使えます。
整数の加算結果が Sum に収まらない場合は std::overflow_error を送出します。採用順の途中の総和も表現できる型を選んでください。浮動小数点の重みに NaN は使えず、総和には通常の丸め誤差があります。
頂点数・辺数はそれぞれ INT_MAX 以下とします。頂点数を \(n\)、辺数を \(m\) とすると、構築全体は \(O(n+m\log m)\) 時間、入力・結果・作業領域を含めて \(O(n+m)\) 空間です。
#ifndef KRUSKAL_HPP
#define KRUSKAL_HPP
#include "../data_structure/union_find.hpp"
#include "../components/radix_sort.hpp"
#include <cstdint>
#include <limits>
#include <stdexcept>
#include <type_traits>
template<class Cost = std::int64_t, class Sum = Cost>
class Kruskal {
static_assert((std::numeric_limits<Cost>::is_integer || std::is_floating_point_v<Cost>) && !std::is_same_v<Cost, bool>);
static_assert((std::numeric_limits<Sum>::is_integer || std::is_floating_point_v<Sum>) && !std::is_same_v<Sum, bool>);
static_assert(std::numeric_limits<Cost>::is_integer == std::numeric_limits<Sum>::is_integer);
static_assert(std::numeric_limits<Cost>::digits <= std::numeric_limits<Sum>::digits);
static_assert(!std::numeric_limits<Cost>::is_signed || std::numeric_limits<Sum>::is_signed);
int n_;
public:
struct Edge { int from, to; Cost cost; };
struct Result {
Sum cost = 0;
std::vector<int> edges;
int components = 0;
bool connected() const { return components <= 1; }
};
private:
std::vector<Edge> edges_;
static void add_cost(Sum &sum, Cost value) {
if constexpr (std::numeric_limits<Cost>::is_integer) {
#if defined(__GNUC__) || defined(__clang__)
Sum next;
if (__builtin_add_overflow(sum, value, &next)) throw std::overflow_error("Kruskal cost");
sum = next;
#else
if ((value > 0 && sum > std::numeric_limits<Sum>::max() - value) ||
(std::numeric_limits<Cost>::is_signed && value < 0 && sum < std::numeric_limits<Sum>::lowest() - value))
throw std::overflow_error("Kruskal cost");
sum += value;
#endif
} else sum += value;
}
public:
explicit Kruskal(int n = 0) : n_(n) { assert(n >= 0); }
int size() const { return n_; }
int edge_count() const { return static_cast<int>(edges_.size()); }
void reserve(std::size_t m) {
assert(m <= std::size_t(std::numeric_limits<int>::max()));
edges_.reserve(m);
}
int add_edge(int u, int v, Cost cost) {
assert(0 <= u && u < n_ && 0 <= v && v < n_);
assert(edges_.size() < std::size_t(std::numeric_limits<int>::max()));
if constexpr (std::is_floating_point_v<Cost>) assert(cost == cost);
int id = edge_count();
edges_.push_back({u, v, cost});
return id;
}
Edge get_edge(int id) const {
assert(0 <= id && id < edge_count());
return edges_[id];
}
const std::vector<Edge> &edges() const { return edges_; }
void clear() { edges_.clear(); }
Result solve() const {
std::vector<int> order;
order.reserve(edges_.size());
for (int id = 0; id < edge_count(); ++id) if (edges_[id].from != edges_[id].to) order.push_back(id);
auto compare = [&](int a, int b) {
if (edges_[a].cost < edges_[b].cost) return true;
if (edges_[b].cost < edges_[a].cost) return false;
return a < b;
};
if constexpr (std::is_integral_v<Cost> && sizeof(Cost) <= sizeof(std::uint64_t)) {
if (order.size() < 256) std::sort(order.begin(), order.end(), compare);
else if (std::is_sorted(order.begin(), order.end(), [&](int a, int b) { return edges_[b].cost < edges_[a].cost; })) {
std::reverse(order.begin(), order.end());
for (std::size_t first = 0; first < order.size();) {
std::size_t last = first + 1;
while (last < order.size() && edges_[order[first]].cost == edges_[order[last]].cost) ++last;
std::reverse(order.begin() + first, order.begin() + last);
first = last;
}
} else {
using Key = std::make_unsigned_t<Cost>;
auto sort_key = [&](int id) {
Key key = static_cast<Key>(edges_[id].cost);
if constexpr (std::is_signed_v<Cost>) key ^= Key(1) << (std::numeric_limits<Key>::digits - 1);
return key;
};
if (sizeof(Cost) >= 4 && order.size() >= 65536) stable_radix_sort<11>(order, sort_key);
else stable_radix_sort(order, sort_key);
}
} else std::sort(order.begin(), order.end(), compare);
UnionFind uf(n_);
Result result;
result.edges.reserve(std::min(order.size(), std::size_t(n_ ? n_ - 1 : 0)));
for (int id : order) {
const auto &e = edges_[id];
if constexpr (std::is_same_v<Cost, std::int64_t> && std::is_same_v<Sum, Cost>) {
int components = uf.count();
uf.merge(e.from, e.to);
if (uf.count() == components) continue;
add_cost(result.cost, e.cost);
} else {
int u = uf.leader(e.from), v = uf.leader(e.to);
if (u == v) continue;
add_cost(result.cost, e.cost);
uf.merge(u, v);
}
result.edges.push_back(id);
if (uf.count() == 1) break;
}
result.components = uf.count();
return result;
}
};
#endif
#line 1 "graph/kruskal.hpp"
#line 1 "data_structure/union_find.hpp"
#include <algorithm>
#include <cassert>
#include <vector>
class UnionFind {
std::vector<int> parent_;
int count_ = 0;
public:
explicit UnionFind(int n = 0) : count_(n) {
assert(n >= 0);
parent_.assign(n, -1);
}
[[nodiscard]] int size() const { return static_cast<int>(parent_.size()); }
[[nodiscard]] int count() const { return count_; }
[[nodiscard]] int leader(int v) {
assert(0 <= v && v < size());
while (parent_[v] >= 0) {
int p = parent_[v];
if (parent_[p] < 0) return p;
v = parent_[v] = parent_[p];
}
return v;
}
int merge(int a, int b) {
a = leader(a), b = leader(b);
if (a == b) return a;
if (parent_[a] > parent_[b]) std::swap(a, b);
parent_[a] += parent_[b];
parent_[b] = a;
--count_;
return a;
}
[[nodiscard]] bool same(int a, int b) { return leader(a) == leader(b); }
[[nodiscard]] int size(int v) { return -parent_[leader(v)]; }
[[nodiscard]] std::vector<std::vector<int>> groups() {
std::vector<int> index(size(), -1);
std::vector<std::vector<int>> result(count_);
int next = 0;
for (int v = 0; v < size(); ++v) if (parent_[v] < 0) {
index[v] = next;
result[next++].reserve(-parent_[v]);
}
for (int v = 0; v < size(); ++v) result[index[leader(v)]].push_back(v);
return result;
}
};
#line 1 "components/radix_sort.hpp"
#line 5 "components/radix_sort.hpp"
#include <array>
#include <cstddef>
#include <cstdint>
#include <type_traits>
#include <utility>
#line 11 "components/radix_sort.hpp"
template <int Bits, class T, class Key>
void stable_radix_sort(std::vector<T> &values, Key &&key) {
static_assert(1 <= Bits && Bits <= 16);
constexpr unsigned buckets = 1U << Bits, mask = buckets - 1;
using K = std::remove_cvref_t<decltype(key(std::declval<const T &>()))>;
static_assert(std::is_integral_v<K> && std::is_unsigned_v<K> && sizeof(K) <= sizeof(std::uint64_t));
if (values.size() < 2) return;
if (values.size() < 256) {
std::stable_sort(values.begin(), values.end(), [&](const T &a, const T &b) { return key(a) < key(b); });
return;
}
std::uint64_t first = key(values.front()), previous = first, varying = 0;
bool sorted = true;
for (std::size_t i = 1; i < values.size(); ++i) {
std::uint64_t current = key(values[i]);
varying |= first ^ current;
sorted &= previous <= current;
previous = current;
}
if (sorted) return;
std::vector<T> scratch(values.size());
using Counts = std::conditional_t<(Bits <= 11), std::array<std::size_t, buckets>, std::vector<std::size_t>>;
Counts count = [] {
if constexpr (Bits <= 11) return Counts{};
else return Counts(buckets);
}();
for (int shift = 0; shift < 64; shift += Bits) {
if (!((varying >> shift) & mask)) continue;
std::fill(count.begin(), count.end(), 0);
for (const auto &value : values) ++count[(static_cast<std::uint64_t>(key(value)) >> shift) & mask];
std::size_t sum = 0;
for (auto &x : count) {
auto n = x;
x = sum, sum += n;
}
for (auto &&value : values) scratch[count[(static_cast<std::uint64_t>(key(value)) >> shift) & mask]++] = std::move(value);
values.swap(scratch);
}
}
template <class T, class Key>
void stable_radix_sort(std::vector<T> &values, Key key) {
stable_radix_sort<8>(values, std::move(key));
}
namespace radix_sort_detail {
template <int Bits = 8, class T, class Key>
void sort_by_key(std::vector<T> &values, Key key) {
using K = std::remove_cvref_t<decltype(key(std::declval<const T &>()))>;
if constexpr (std::is_integral_v<K> && !std::is_same_v<K, bool> && sizeof(K) <= 8 && std::is_default_constructible_v<T>) {
using U = std::make_unsigned_t<K>;
constexpr U sign = std::is_signed_v<K> ? U(1) << (sizeof(K) * 8 - 1) : U(0);
stable_radix_sort<Bits>(values, [&](const T &value) { return U(U(key(value)) ^ sign); });
} else std::sort(values.begin(), values.end(), [&](const T &a, const T &b) { return key(a) < key(b); });
}
}
#line 7 "graph/kruskal.hpp"
#include <limits>
#include <stdexcept>
#line 10 "graph/kruskal.hpp"
template<class Cost = std::int64_t, class Sum = Cost>
class Kruskal {
static_assert((std::numeric_limits<Cost>::is_integer || std::is_floating_point_v<Cost>) && !std::is_same_v<Cost, bool>);
static_assert((std::numeric_limits<Sum>::is_integer || std::is_floating_point_v<Sum>) && !std::is_same_v<Sum, bool>);
static_assert(std::numeric_limits<Cost>::is_integer == std::numeric_limits<Sum>::is_integer);
static_assert(std::numeric_limits<Cost>::digits <= std::numeric_limits<Sum>::digits);
static_assert(!std::numeric_limits<Cost>::is_signed || std::numeric_limits<Sum>::is_signed);
int n_;
public:
struct Edge { int from, to; Cost cost; };
struct Result {
Sum cost = 0;
std::vector<int> edges;
int components = 0;
bool connected() const { return components <= 1; }
};
private:
std::vector<Edge> edges_;
static void add_cost(Sum &sum, Cost value) {
if constexpr (std::numeric_limits<Cost>::is_integer) {
#if defined(__GNUC__) || defined(__clang__)
Sum next;
if (__builtin_add_overflow(sum, value, &next)) throw std::overflow_error("Kruskal cost");
sum = next;
#else
if ((value > 0 && sum > std::numeric_limits<Sum>::max() - value) ||
(std::numeric_limits<Cost>::is_signed && value < 0 && sum < std::numeric_limits<Sum>::lowest() - value))
throw std::overflow_error("Kruskal cost");
sum += value;
#endif
} else sum += value;
}
public:
explicit Kruskal(int n = 0) : n_(n) { assert(n >= 0); }
int size() const { return n_; }
int edge_count() const { return static_cast<int>(edges_.size()); }
void reserve(std::size_t m) {
assert(m <= std::size_t(std::numeric_limits<int>::max()));
edges_.reserve(m);
}
int add_edge(int u, int v, Cost cost) {
assert(0 <= u && u < n_ && 0 <= v && v < n_);
assert(edges_.size() < std::size_t(std::numeric_limits<int>::max()));
if constexpr (std::is_floating_point_v<Cost>) assert(cost == cost);
int id = edge_count();
edges_.push_back({u, v, cost});
return id;
}
Edge get_edge(int id) const {
assert(0 <= id && id < edge_count());
return edges_[id];
}
const std::vector<Edge> &edges() const { return edges_; }
void clear() { edges_.clear(); }
Result solve() const {
std::vector<int> order;
order.reserve(edges_.size());
for (int id = 0; id < edge_count(); ++id) if (edges_[id].from != edges_[id].to) order.push_back(id);
auto compare = [&](int a, int b) {
if (edges_[a].cost < edges_[b].cost) return true;
if (edges_[b].cost < edges_[a].cost) return false;
return a < b;
};
if constexpr (std::is_integral_v<Cost> && sizeof(Cost) <= sizeof(std::uint64_t)) {
if (order.size() < 256) std::sort(order.begin(), order.end(), compare);
else if (std::is_sorted(order.begin(), order.end(), [&](int a, int b) { return edges_[b].cost < edges_[a].cost; })) {
std::reverse(order.begin(), order.end());
for (std::size_t first = 0; first < order.size();) {
std::size_t last = first + 1;
while (last < order.size() && edges_[order[first]].cost == edges_[order[last]].cost) ++last;
std::reverse(order.begin() + first, order.begin() + last);
first = last;
}
} else {
using Key = std::make_unsigned_t<Cost>;
auto sort_key = [&](int id) {
Key key = static_cast<Key>(edges_[id].cost);
if constexpr (std::is_signed_v<Cost>) key ^= Key(1) << (std::numeric_limits<Key>::digits - 1);
return key;
};
if (sizeof(Cost) >= 4 && order.size() >= 65536) stable_radix_sort<11>(order, sort_key);
else stable_radix_sort(order, sort_key);
}
} else std::sort(order.begin(), order.end(), compare);
UnionFind uf(n_);
Result result;
result.edges.reserve(std::min(order.size(), std::size_t(n_ ? n_ - 1 : 0)));
for (int id : order) {
const auto &e = edges_[id];
if constexpr (std::is_same_v<Cost, std::int64_t> && std::is_same_v<Sum, Cost>) {
int components = uf.count();
uf.merge(e.from, e.to);
if (uf.count() == components) continue;
add_cost(result.cost, e.cost);
} else {
int u = uf.leader(e.from), v = uf.leader(e.to);
if (u == v) continue;
add_cost(result.cost, e.cost);
uf.merge(u, v);
}
result.edges.push_back(id);
if (uf.count() == 1) break;
}
result.components = uf.count();
return result;
}
};