競技プログラミング用 C++ ライブラリ
graph/topological_sort.hpp · GitHub で見る
共通ページ
有向グラフの全頂点を、各辺の始点が終点より前になるように並べます。閉路がある場合は std::nullopt を返します。
#include "lib/graph/topological_sort.hpp"
#include <iostream>
std::vector<std::vector<int>> graph{ {}, {}, {0}, {1} };
auto order = topological_sort(graph); // {2, 3, 0, 1}
auto smallest = topological_sort<true>(graph); // {2, 0, 3, 1}
if (order) {
for (int v : *order) {
std::cout << v << ' ';
}
}
graph[0].push_back(2);
auto cyclic = topological_sort(graph); // std::nullopt
| 関数 | 返り値 | 時間計算量 |
|---|---|---|
topological_sort(graph) |
トポロジカル順序を格納した optional<vector<int>>
|
\(O(n+m)\) |
topological_sort<true>(graph) |
辞書順最小のトポロジカル順序 | \(O(m+n\log n)\) |
入力は vector<vector<int>> などの隣接リストです。graph.size() で頂点数を取得でき、graph[v] が行き先の int を列挙できれば、array・span なども渡せます。頂点番号は 0 以上 n 未満、頂点数は INT_MAX 以下としてください。辺 u -> v は graph[u].push_back(v) で登録します。
非連結グラフや孤立点も含めて全頂点を返します。空グラフでは、空の vector<int> を格納した有効な optional を返します。多重辺はそのまま扱えます。自己辺は閉路と判定します。一部にだけ閉路がある場合も nullopt で、処理途中の順序は返しません。
通常版は入次数 0 の頂点を FIFO で処理します。最初の候補は頂点番号順に追加し、その後は入力の隣接リスト順に追加します。辞書順最小が必要な場合は <true> を指定してください。この場合は常に候補の最小頂点を選び、隣接リスト内の辺の順序に依存しません。
Kahn のアルゴリズムを使い、通常版では出力配列をキューとして兼用します。入力は変更せず、再帰は使いません。返された結果は入力を参照しないため、同じグラフに何度でも適用できます。
頂点数を \(n\)、辺数を \(m\) とすると、通常版は \(O(n+m)\) 時間、辞書順最小版は \(O(m+n\log n)\) 時間です。どちらも出力を含めて \(O(n)\) の追加空間を使います。
#ifndef TOPOLOGICAL_SORT_HPP
#define TOPOLOGICAL_SORT_HPP
#include "../components/predecessor_set.hpp"
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <functional>
#include <limits>
#include <optional>
#include <vector>
template<bool Lexicographical = false, class G>
std::optional<std::vector<int>> topological_sort(const G &graph) {
assert(graph.size() <= std::size_t(std::numeric_limits<int>::max()));
int n = static_cast<int>(graph.size());
std::vector<std::size_t> indegree(n);
for (int v = 0; v < n; ++v) for (int to : graph[v]) {
assert(0 <= to && to < n);
++indegree[to];
}
std::vector<int> order;
order.reserve(n);
if constexpr (Lexicographical) {
std::vector<int> ready;
for (int v = 0; v < n; ++v) if (!indegree[v]) ready.push_back(v);
std::optional<PredecessorSet> ready_set;
bool use_set = ready.size() > 128;
if (use_set) {
ready_set.emplace(n);
for (int v : ready) ready_set->insert(v);
ready.clear();
} else std::make_heap(ready.begin(), ready.end(), std::greater<int>{});
while (use_set || !ready.empty()) {
int v;
if (use_set) {
v = ready_set->min();
ready_set->erase(v);
} else {
std::pop_heap(ready.begin(), ready.end(), std::greater<int>{});
v = ready.back();
ready.pop_back();
}
order.push_back(v);
for (int to : graph[v]) if (--indegree[to] == 0) {
if (use_set) ready_set->insert(to);
else {
ready.push_back(to);
std::push_heap(ready.begin(), ready.end(), std::greater<int>{});
}
}
if (use_set && ready_set->size() <= 32) {
while (!ready_set->empty()) {
int x = ready_set->min();
ready_set->erase(x);
ready.push_back(x);
}
std::make_heap(ready.begin(), ready.end(), std::greater<int>{});
use_set = false;
} else if (!use_set && ready.size() > 128) {
if (!ready_set) ready_set.emplace(n);
for (int x : ready) ready_set->insert(x);
ready.clear();
use_set = true;
}
}
} else {
for (int v = 0; v < n; ++v) if (!indegree[v]) order.push_back(v);
for (std::size_t head = 0; head < order.size(); ++head)
for (int to : graph[order[head]]) if (--indegree[to] == 0) order.push_back(to);
}
if (order.size() != std::size_t(n)) return std::nullopt;
return order;
}
#endif
#line 1 "graph/topological_sort.hpp"
#line 1 "components/predecessor_set.hpp"
#line 1 "components/xor_select.hpp"
#include <bit>
#include <cassert>
#include <cstdint>
namespace xor_select_detail {
inline unsigned argmin(std::uint64_t word, unsigned key) {
assert(word && key < 64);
if (std::has_single_bit(word)) return std::countr_zero(word);
if ((word >> key) & 1) return key;
unsigned shift = key & 32;
word = std::rotl(word, shift);
auto exchange = [&](unsigned shift, std::uint64_t mask) {
word = ((word & mask) << shift) | ((word & ~mask) >> shift);
};
exchange(key & 16, 0x0000ffff0000ffffULL);
exchange(key & 8, 0x00ff00ff00ff00ffULL);
exchange(key & 4, 0x0f0f0f0f0f0f0f0fULL);
exchange(key & 2, 0x3333333333333333ULL);
exchange(key & 1, 0x5555555555555555ULL);
return std::countr_zero(word) ^ key;
}
}
#line 5 "components/predecessor_set.hpp"
#include <algorithm>
#include <array>
#line 9 "components/predecessor_set.hpp"
#include <cstddef>
#line 11 "components/predecessor_set.hpp"
#include <cstring>
#include <limits>
#include <span>
#include <string_view>
#include <utility>
#include <vector>
#if defined(__x86_64__) && (defined(__GNUC__) || defined(__clang__))
#include <immintrin.h>
#endif
namespace predecessor_set_detail {
inline void pack_scalar(const char *src, std::uint64_t *dst, std::size_t words) {
for (std::size_t i = 0; i < words; ++i) {
std::uint64_t word = 0;
if constexpr (std::endian::native == std::endian::little) {
for (int j = 0; j < 8; ++j) {
std::uint64_t x;
std::memcpy(&x, src + i * 64 + j * 8, 8);
assert((x & 0xfefefefefefefefeULL) == 0x3030303030303030ULL);
word |= ((x & 0x0101010101010101ULL) * 0x0102040810204080ULL >> 56) << (j * 8);
}
} else {
for (int j = 0; j < 64; ++j) {
assert(src[i * 64 + j] == '0' || src[i * 64 + j] == '1');
word |= std::uint64_t(src[i * 64 + j] - '0') << j;
}
}
dst[i] = word;
}
}
#if defined(__x86_64__) && (defined(__GNUC__) || defined(__clang__))
__attribute__((target("avx2"))) inline void pack_avx2(const char *src, std::uint64_t *dst, std::size_t words) {
const auto ones = _mm256_set1_epi8('1');
for (std::size_t i = 0; i < words; ++i) {
auto a = _mm256_loadu_si256(reinterpret_cast<const __m256i *>(src + i * 64));
auto b = _mm256_loadu_si256(reinterpret_cast<const __m256i *>(src + i * 64 + 32));
assert(_mm256_movemask_epi8(_mm256_cmpeq_epi8(_mm256_and_si256(a, _mm256_set1_epi8(-2)), _mm256_set1_epi8('0'))) == -1);
assert(_mm256_movemask_epi8(_mm256_cmpeq_epi8(_mm256_and_si256(b, _mm256_set1_epi8(-2)), _mm256_set1_epi8('0'))) == -1);
auto low = std::uint32_t(_mm256_movemask_epi8(_mm256_cmpeq_epi8(a, ones)));
auto high = std::uint32_t(_mm256_movemask_epi8(_mm256_cmpeq_epi8(b, ones)));
dst[i] = low | (std::uint64_t(high) << 32);
}
}
#endif
inline void pack(const char *src, std::uint64_t *dst, std::size_t words) {
#if defined(__x86_64__) && (defined(__GNUC__) || defined(__clang__))
if (words >= 4 && __builtin_cpu_supports("avx2")) return pack_avx2(src, dst, words);
#endif
pack_scalar(src, dst, words);
}
}
class PredecessorSet {
int n = 0, count = 0, height = 0;
std::array<int, 6> offset{}, length{};
std::array<int, 16> small{};
bool small_valid = false;
std::vector<std::uint64_t> data;
void mask_tail() {
int bits = n;
for (int h = 0; h < height; ++h) {
if (bits & 63) data[offset[h] + length[h] - 1] &= ~std::uint64_t(0) >> (64 - (bits & 63));
bits = length[h];
}
}
void rebuild() {
count = 0;
for (int i = 0; i < length[0]; ++i) count += std::popcount(data[i]);
for (int h = 1; h < height; ++h)
for (int i = 0; i < length[h - 1]; ++i)
if (data[offset[h - 1] + i]) data[offset[h] + (i >> 6)] |= std::uint64_t(1) << (i & 63);
rebuild_small();
}
template <int H, bool Forward> int descend(int p) const {
if constexpr (H == 0) return p;
else {
auto word = data[offset[H - 1] + p];
int bit = Forward ? std::countr_zero(word) : 63 - std::countl_zero(word);
return descend<H - 1, Forward>(p * 64 + bit);
}
}
template <int H, bool Forward> int ascend(int p) const {
if constexpr (H == 6) return Forward ? n : -1;
else {
if (H >= height) return Forward ? n : -1;
auto word = data[offset[H] + (p >> 6)];
auto mask = Forward ? (~std::uint64_t(1) << (p & 63)) : ~(~std::uint64_t(0) << (p & 63));
word &= mask;
if (word) {
int bit = Forward ? std::countr_zero(word) : 63 - std::countl_zero(word);
return descend<H, Forward>((p & ~63) + bit);
}
return ascend<H + 1, Forward>(p >> 6);
}
}
void rebuild_small() {
small_valid = count <= int(small.size());
if (!small_valid) return;
small.fill(n);
for (int i = 0, p = 0; i < count; ++i) {
auto word = data[p >> 6] >> (p & 63);
p = word ? p + std::countr_zero(word) : ascend<1, true>(p >> 6);
small[i] = p++;
}
}
int small_rank(int p) const {
#if defined(__AVX512F__) && defined(__x86_64__)
auto keys = _mm512_loadu_si512(small.data());
return std::popcount(unsigned(_mm512_cmp_epi32_mask(keys, _mm512_set1_epi32(p), _MM_CMPINT_LT)));
#elif defined(__AVX2__) && defined(__x86_64__)
auto key = _mm256_set1_epi32(p);
auto a = _mm256_cmpgt_epi32(key, _mm256_loadu_si256(reinterpret_cast<const __m256i *>(small.data())));
auto b = _mm256_cmpgt_epi32(key, _mm256_loadu_si256(reinterpret_cast<const __m256i *>(small.data() + 8)));
return std::popcount(unsigned(_mm256_movemask_ps(_mm256_castsi256_ps(a)))) + std::popcount(unsigned(_mm256_movemask_ps(_mm256_castsi256_ps(b))));
#else
int result = 0;
for (int x : small) result += x < p;
return result;
#endif
}
public:
int argmin_xor(std::uint32_t x) const {
if (!count) return -1;
std::uint64_t p = std::uint64_t(x) & ((std::uint64_t(1) << (height * 6)) - 1);
for (int h = 0; h < height; ++h, p >>= 6) {
auto index = p >> 6;
if (index >= unsigned(length[h])) continue;
auto word = data[offset[h] + index];
if (!word) continue;
p = index * 64 + xor_select_detail::argmin(word, p & 63);
while (h--) p = p * 64 + xor_select_detail::argmin(data[offset[h] + p], (x >> (h * 6)) & 63);
return int(p);
}
return -1;
}
int argmax_xor(std::uint32_t x) const { return argmin_xor(~x); }
explicit PredecessorSet(int n = 0, bool filled = false) { assign(n, filled); }
explicit PredecessorSet(std::string_view bits) { assign(bits); }
PredecessorSet(int n, std::span<const std::uint64_t> words) { assign(n, words); }
PredecessorSet(const PredecessorSet &) = default;
PredecessorSet &operator=(const PredecessorSet &) = default;
PredecessorSet(PredecessorSet &&other) noexcept : PredecessorSet() { swap(other); }
PredecessorSet &operator=(PredecessorSet &&other) noexcept {
if (this != &other) { PredecessorSet moved(std::move(other)); swap(moved); }
return *this;
}
void assign(int length, bool filled = false) {
assert(length >= 0);
n = length; count = filled ? n : 0; height = 0;
offset.fill(0); this->length.fill(0);
int total = 0;
for (std::size_t bits = n; bits;) {
std::size_t words = (bits + 63) / 64;
offset[height] = total; this->length[height++] = words;
total += words;
if (words == 1) break;
bits = words;
}
data.assign(total, filled ? ~std::uint64_t(0) : 0);
if (filled) mask_tail();
rebuild_small();
}
void assign(std::string_view bits) {
assert(bits.size() <= std::size_t(std::numeric_limits<int>::max()));
PredecessorSet next(static_cast<int>(bits.size()));
std::size_t words = bits.size() / 64;
predecessor_set_detail::pack(bits.data(), next.data.data(), words);
for (std::size_t i = words * 64; i < bits.size(); ++i) {
assert(bits[i] == '0' || bits[i] == '1');
next.data[i >> 6] |= std::uint64_t(bits[i] - '0') << (i & 63);
}
next.rebuild(); swap(next);
}
void assign(int n, std::span<const std::uint64_t> words) {
assert(n >= 0 && words.size() == (std::size_t(n) + 63) / 64);
PredecessorSet next(n);
std::copy(words.begin(), words.end(), next.data.begin());
if (n & 63) next.data[next.length[0] - 1] &= ~std::uint64_t(0) >> (64 - (n & 63));
next.rebuild(); swap(next);
}
int universe_size() const { return n; }
int size() const { return count; }
bool empty() const { return count == 0; }
bool contains(int p) const { assert(0 <= p && p < n); return data[p >> 6] >> (p & 63) & 1; }
bool insert(int p) {
assert(0 <= p && p < n);
int index = p >> 6;
auto &leaf = data[index];
auto mask = std::uint64_t(1) << (p & 63), before = leaf;
leaf = before | mask;
bool changed = !(before & mask);
count += changed;
if (!before) for (int h = 1; h < height; ++h) {
auto &word = data[offset[h] + (index >> 6)];
auto old = word;
word |= std::uint64_t(1) << (index & 63);
if (old) break;
index >>= 6;
}
small_valid = false;
return changed;
}
bool erase(int p) {
assert(0 <= p && p < n);
int index = p >> 6;
auto &leaf = data[index];
auto mask = std::uint64_t(1) << (p & 63), before = leaf;
leaf = before & ~mask;
bool changed = (before & mask) != 0;
count -= changed;
if (before && !leaf) for (int h = 1; h < height; ++h) {
auto &word = data[offset[h] + (index >> 6)];
word &= ~(std::uint64_t(1) << (index & 63));
if (word) break;
index >>= 6;
}
small_valid = false;
return changed;
}
int next(int p) const {
p = std::max(p, 0);
if (p >= n || !count) return n;
if (small_valid) {
int index = small_rank(p);
return index == count ? n : small[index];
}
auto local = data[p >> 6] >> (p & 63);
if (local) return p + std::countr_zero(local);
return ascend<1, true>(p >> 6);
}
int prev(int p) const {
p = std::min(p, n - 1);
if (p < 0 || !count) return -1;
if (small_valid) {
int index = small_rank(p + 1);
return index ? small[index - 1] : -1;
}
auto local = data[p >> 6] << (63 - (p & 63));
if (local) return p - std::countl_zero(local);
return ascend<1, false>(p >> 6);
}
bool set(int p, bool present) {
assert(0 <= p && p < n);
int index = p >> 6;
auto &leaf = data[index];
auto mask = std::uint64_t(1) << (p & 63), before = leaf;
auto fill = std::uint64_t(0) - present;
leaf = before ^ ((before ^ fill) & mask);
bool old = (before & mask) != 0;
count += int(present) - int(old);
if ((!before) != (!leaf)) for (int h = 1; h < height; ++h) {
auto &word = data[offset[h] + (index >> 6)];
auto previous = word;
word = previous ^ ((previous ^ fill) & (std::uint64_t(1) << (index & 63)));
if ((!previous) == (!word)) break;
index >>= 6;
}
small_valid = false;
return old != present;
}
int min() const { return next(0); }
int max() const { return prev(n - 1); }
void clear() { std::fill(data.begin(), data.end(), 0); count = 0; small_valid = false; }
void fill() {
std::fill(data.begin(), data.end(), ~std::uint64_t(0));
mask_tail();
count = n;
rebuild_small();
}
template <class F> void for_each(int l, int r, F &&f) const {
assert(0 <= l && l <= r && r <= n);
for (int p = next(l); p < r;) {
int base = p & ~63;
auto word = data[p >> 6] & (~std::uint64_t(0) << (p & 63));
if (r - base < 64) word &= ~std::uint64_t(0) >> (64 - (r - base));
while (word) { f(base + std::countr_zero(word)); word &= word - 1; }
if (r - base <= 64) break;
p = next(base + 64);
}
}
template <class F> void for_each(F &&f) const { for_each(0, n, f); }
std::vector<int> to_vector() const {
std::vector<int> out; out.reserve(count);
for_each([&](int x) { out.push_back(x); }); return out;
}
std::span<const std::uint64_t> words() const & { return {data.data(), std::size_t(length[0])}; }
void words() const && = delete;
void swap(PredecessorSet &other) noexcept {
using std::swap;
swap(n, other.n); swap(count, other.count); swap(height, other.height);
offset.swap(other.offset); length.swap(other.length); data.swap(other.data); small.swap(other.small); swap(small_valid, other.small_valid);
}
std::size_t memory_usage() const { return data.capacity() * sizeof(std::uint64_t); }
};
#line 8 "graph/topological_sort.hpp"
#include <functional>
#line 10 "graph/topological_sort.hpp"
#include <optional>
#line 12 "graph/topological_sort.hpp"
template<bool Lexicographical = false, class G>
std::optional<std::vector<int>> topological_sort(const G &graph) {
assert(graph.size() <= std::size_t(std::numeric_limits<int>::max()));
int n = static_cast<int>(graph.size());
std::vector<std::size_t> indegree(n);
for (int v = 0; v < n; ++v) for (int to : graph[v]) {
assert(0 <= to && to < n);
++indegree[to];
}
std::vector<int> order;
order.reserve(n);
if constexpr (Lexicographical) {
std::vector<int> ready;
for (int v = 0; v < n; ++v) if (!indegree[v]) ready.push_back(v);
std::optional<PredecessorSet> ready_set;
bool use_set = ready.size() > 128;
if (use_set) {
ready_set.emplace(n);
for (int v : ready) ready_set->insert(v);
ready.clear();
} else std::make_heap(ready.begin(), ready.end(), std::greater<int>{});
while (use_set || !ready.empty()) {
int v;
if (use_set) {
v = ready_set->min();
ready_set->erase(v);
} else {
std::pop_heap(ready.begin(), ready.end(), std::greater<int>{});
v = ready.back();
ready.pop_back();
}
order.push_back(v);
for (int to : graph[v]) if (--indegree[to] == 0) {
if (use_set) ready_set->insert(to);
else {
ready.push_back(to);
std::push_heap(ready.begin(), ready.end(), std::greater<int>{});
}
}
if (use_set && ready_set->size() <= 32) {
while (!ready_set->empty()) {
int x = ready_set->min();
ready_set->erase(x);
ready.push_back(x);
}
std::make_heap(ready.begin(), ready.end(), std::greater<int>{});
use_set = false;
} else if (!use_set && ready.size() > 128) {
if (!ready_set) ready_set.emplace(n);
for (int x : ready) ready_set->insert(x);
ready.clear();
use_set = true;
}
}
} else {
for (int v = 0; v < n; ++v) if (!indegree[v]) order.push_back(v);
for (std::size_t head = 0; head < order.size(); ++head)
for (int to : graph[order[head]]) if (--indegree[to] == 0) order.push_back(to);
}
if (order.size() != std::size_t(n)) return std::nullopt;
return order;
}