競技プログラミング用 C++ ライブラリ
data_structure/xor_set.hpp · GitHub で見る
共通ページ
整数の追加・削除と、指定値との XOR が最小/最大になる要素の取得に対応する集合です。同じ値を追加しても重複しません。
#include "lib/data_structure/xor_set.hpp"
XorSet<30> values;
values.insert(3);
values.insert(10);
auto a = values.argmin_xor(8); // 10
auto b = values.min_xor(8); // 2
auto c = values.argmax_xor(8); // 3
auto d = values.max_xor(8); // 11
values.erase(3);
XorSet<Bits> の Bits は既定で 30、範囲は 1〜31 です。格納できる値は uint32_t の [0,2^Bits)、XOR の問い合わせには任意の uint32_t を渡せます。
| 操作 | 内容・返り値 |
|---|---|
XorSet<Bits>() |
空集合を作成 |
size() / empty()
|
要素数を int64_t で返す/空判定 |
contains(x) |
x が含まれるか |
insert(x) |
x を追加し、追加できた場合に true
|
erase(x) |
x を削除し、含まれていた場合に true
|
argmin_xor(x) / argmax_xor(x)
|
x との XOR が最小/最大になる集合の要素を uint32_t で返す |
min_xor(x) / max_xor(x)
|
x と集合の要素との XOR の最小値/最大値を uint32_t で返す |
clear() |
容量を保って空集合にする |
swap(other) |
内容を交換 |
memory_usage() |
確保済みの動的領域のバイト数 |
argmin_xor・argmax_xor・min_xor・max_xor は空でない集合に対して呼んでください。コピーは独立した集合を作り、移動元は再利用できる空集合になります。
\(U=2^{Bits}\)、これまでに確保した葉の最大数を \(L\) とします。最初の挿入は管理領域の確保に \(O(U/512)\) 時間、その後の挿入は償却 \(O(\log_{64}U)\) 時間、削除と XOR の問い合わせは \(O(\log_{64}U)\) 時間です。contains・size・empty・交換は \(O(1)\)、コピーと clear は \(O(U/512+L)\) 時間、使用空間は \(O(U/512+L)\) 語です。Bits が 9 以下なら管理領域は定数サイズです。
#ifndef XOR_SET_HPP
#define XOR_SET_HPP
#include "../components/predecessor_set.hpp"
#include "../components/xor_select.hpp"
#include <algorithm>
#include <array>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <utility>
#include <vector>
template <int Bits = 30>
class XorSet {
static_assert(1 <= Bits && Bits <= 31);
static constexpr int leaf_bits = std::min(Bits, 9);
static constexpr std::uint32_t leaf_mask = (std::uint32_t(1) << leaf_bits) - 1;
static constexpr std::uint32_t block_count = std::uint32_t(1) << (Bits - leaf_bits);
static constexpr std::uint32_t singleton = std::uint32_t(1) << 31;
struct alignas(64) Leaf {
std::array<std::uint64_t, 8> words{};
};
std::vector<std::uint32_t> directory;
PredecessorSet occupied;
std::vector<Leaf> leaves;
std::uint32_t free_leaf = 0, all_xor = 0;
std::int64_t count = 0;
void ensure_storage() {
if (!directory.empty()) return;
std::vector<std::uint32_t> next(block_count);
PredecessorSet index(block_count);
directory.swap(next);
occupied.swap(index);
}
std::uint32_t make_leaf() {
if (free_leaf) {
auto id = free_leaf;
free_leaf = std::uint32_t(leaves[id].words[0]);
leaves[id] = Leaf{};
return id;
}
if (leaves.empty()) leaves.emplace_back();
leaves.emplace_back();
return std::uint32_t(leaves.size() - 1);
}
void release_leaf(std::uint32_t id) {
leaves[id].words[0] = free_leaf;
free_leaf = id;
}
static void check([[maybe_unused]] std::uint32_t value) { assert(value < (std::uint32_t(1) << Bits)); }
public:
using value_type = std::uint32_t;
XorSet() = default;
XorSet(const XorSet &) = default;
XorSet &operator=(const XorSet &other) {
if (this != &other) { XorSet copy(other); swap(copy); }
return *this;
}
XorSet(XorSet &&other) noexcept
: directory(std::move(other.directory)), occupied(std::move(other.occupied)), leaves(std::move(other.leaves)),
free_leaf(std::exchange(other.free_leaf, 0)), all_xor(std::exchange(other.all_xor, 0)), count(std::exchange(other.count, 0)) {
other.directory.clear(); other.leaves.clear();
}
XorSet &operator=(XorSet &&other) noexcept {
if (this != &other) { XorSet moved(std::move(other)); swap(moved); }
return *this;
}
std::int64_t size() const { return count; }
bool empty() const { return count == 0; }
bool contains(std::uint32_t value) const {
check(value);
if (empty()) return false;
auto entry = directory[value >> leaf_bits];
if (!entry) return false;
if (entry & singleton) return entry == (singleton | (value & leaf_mask));
return leaves[entry].words[(value & leaf_mask) >> 6] >> (value & 63) & 1;
}
bool insert(std::uint32_t value) {
check(value);
ensure_storage();
auto &entry = directory[value >> leaf_bits];
auto suffix = value & leaf_mask;
if (!entry) {
occupied.insert(value >> leaf_bits);
entry = singleton | suffix;
} else if (entry & singleton) {
auto old = entry ^ singleton;
if (old == suffix) return false;
auto id = make_leaf();
leaves[id].words[old >> 6] = std::uint64_t(1) << (old & 63);
leaves[id].words[suffix >> 6] |= std::uint64_t(1) << (suffix & 63);
entry = id;
} else {
auto &word = leaves[entry].words[suffix >> 6];
auto bit = std::uint64_t(1) << (suffix & 63);
if (word & bit) return false;
word |= bit;
}
++count; all_xor ^= value;
return true;
}
bool erase(std::uint32_t value) {
check(value);
if (empty()) return false;
auto &entry = directory[value >> leaf_bits];
if (!entry) return false;
if (entry & singleton) {
if (entry != (singleton | (value & leaf_mask))) return false;
entry = 0;
occupied.erase(value >> leaf_bits);
} else {
auto &leaf = leaves[entry];
auto &word = leaf.words[(value & leaf_mask) >> 6];
auto bit = std::uint64_t(1) << (value & 63);
if (!(word & bit)) return false;
word ^= bit;
std::uint64_t any = 0;
for (auto part : leaf.words) any |= part;
if (!any) {
release_leaf(entry);
entry = 0;
occupied.erase(value >> leaf_bits);
}
}
--count; all_xor ^= value;
return true;
}
std::uint32_t argmin_xor(std::uint32_t value) const {
assert(!empty());
if (count == 1) return all_xor;
auto prefix = (value >> leaf_bits) & (block_count - 1);
if (!directory[prefix]) prefix = occupied.argmin_xor(value >> leaf_bits);
auto entry = directory[prefix];
if (entry & singleton) return (prefix << leaf_bits) | (entry ^ singleton);
const auto &leaf = leaves[entry];
auto preferred = (value & leaf_mask) >> 6;
for (unsigned distance = 0; distance < 8; ++distance) {
auto i = preferred ^ distance;
if (leaf.words[i]) return (prefix << leaf_bits) | (i << 6) | xor_select_detail::argmin(leaf.words[i], value & 63);
}
std::unreachable();
}
std::uint32_t argmax_xor(std::uint32_t value) const { return argmin_xor(~value); }
std::uint32_t min_xor(std::uint32_t value) const { return argmin_xor(value) ^ value; }
std::uint32_t max_xor(std::uint32_t value) const { return argmax_xor(value) ^ value; }
void clear() {
std::fill(directory.begin(), directory.end(), 0);
occupied.clear(); leaves.clear();
free_leaf = all_xor = 0; count = 0;
}
void swap(XorSet &other) noexcept {
directory.swap(other.directory); occupied.swap(other.occupied); leaves.swap(other.leaves);
std::swap(free_leaf, other.free_leaf); std::swap(all_xor, other.all_xor); std::swap(count, other.count);
}
std::size_t memory_usage() const { return directory.capacity() * sizeof(std::uint32_t) + occupied.memory_usage() + leaves.capacity() * sizeof(Leaf); }
};
#endif
#line 1 "data_structure/xor_set.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 13 "data_structure/xor_set.hpp"
template <int Bits = 30>
class XorSet {
static_assert(1 <= Bits && Bits <= 31);
static constexpr int leaf_bits = std::min(Bits, 9);
static constexpr std::uint32_t leaf_mask = (std::uint32_t(1) << leaf_bits) - 1;
static constexpr std::uint32_t block_count = std::uint32_t(1) << (Bits - leaf_bits);
static constexpr std::uint32_t singleton = std::uint32_t(1) << 31;
struct alignas(64) Leaf {
std::array<std::uint64_t, 8> words{};
};
std::vector<std::uint32_t> directory;
PredecessorSet occupied;
std::vector<Leaf> leaves;
std::uint32_t free_leaf = 0, all_xor = 0;
std::int64_t count = 0;
void ensure_storage() {
if (!directory.empty()) return;
std::vector<std::uint32_t> next(block_count);
PredecessorSet index(block_count);
directory.swap(next);
occupied.swap(index);
}
std::uint32_t make_leaf() {
if (free_leaf) {
auto id = free_leaf;
free_leaf = std::uint32_t(leaves[id].words[0]);
leaves[id] = Leaf{};
return id;
}
if (leaves.empty()) leaves.emplace_back();
leaves.emplace_back();
return std::uint32_t(leaves.size() - 1);
}
void release_leaf(std::uint32_t id) {
leaves[id].words[0] = free_leaf;
free_leaf = id;
}
static void check([[maybe_unused]] std::uint32_t value) { assert(value < (std::uint32_t(1) << Bits)); }
public:
using value_type = std::uint32_t;
XorSet() = default;
XorSet(const XorSet &) = default;
XorSet &operator=(const XorSet &other) {
if (this != &other) { XorSet copy(other); swap(copy); }
return *this;
}
XorSet(XorSet &&other) noexcept
: directory(std::move(other.directory)), occupied(std::move(other.occupied)), leaves(std::move(other.leaves)),
free_leaf(std::exchange(other.free_leaf, 0)), all_xor(std::exchange(other.all_xor, 0)), count(std::exchange(other.count, 0)) {
other.directory.clear(); other.leaves.clear();
}
XorSet &operator=(XorSet &&other) noexcept {
if (this != &other) { XorSet moved(std::move(other)); swap(moved); }
return *this;
}
std::int64_t size() const { return count; }
bool empty() const { return count == 0; }
bool contains(std::uint32_t value) const {
check(value);
if (empty()) return false;
auto entry = directory[value >> leaf_bits];
if (!entry) return false;
if (entry & singleton) return entry == (singleton | (value & leaf_mask));
return leaves[entry].words[(value & leaf_mask) >> 6] >> (value & 63) & 1;
}
bool insert(std::uint32_t value) {
check(value);
ensure_storage();
auto &entry = directory[value >> leaf_bits];
auto suffix = value & leaf_mask;
if (!entry) {
occupied.insert(value >> leaf_bits);
entry = singleton | suffix;
} else if (entry & singleton) {
auto old = entry ^ singleton;
if (old == suffix) return false;
auto id = make_leaf();
leaves[id].words[old >> 6] = std::uint64_t(1) << (old & 63);
leaves[id].words[suffix >> 6] |= std::uint64_t(1) << (suffix & 63);
entry = id;
} else {
auto &word = leaves[entry].words[suffix >> 6];
auto bit = std::uint64_t(1) << (suffix & 63);
if (word & bit) return false;
word |= bit;
}
++count; all_xor ^= value;
return true;
}
bool erase(std::uint32_t value) {
check(value);
if (empty()) return false;
auto &entry = directory[value >> leaf_bits];
if (!entry) return false;
if (entry & singleton) {
if (entry != (singleton | (value & leaf_mask))) return false;
entry = 0;
occupied.erase(value >> leaf_bits);
} else {
auto &leaf = leaves[entry];
auto &word = leaf.words[(value & leaf_mask) >> 6];
auto bit = std::uint64_t(1) << (value & 63);
if (!(word & bit)) return false;
word ^= bit;
std::uint64_t any = 0;
for (auto part : leaf.words) any |= part;
if (!any) {
release_leaf(entry);
entry = 0;
occupied.erase(value >> leaf_bits);
}
}
--count; all_xor ^= value;
return true;
}
std::uint32_t argmin_xor(std::uint32_t value) const {
assert(!empty());
if (count == 1) return all_xor;
auto prefix = (value >> leaf_bits) & (block_count - 1);
if (!directory[prefix]) prefix = occupied.argmin_xor(value >> leaf_bits);
auto entry = directory[prefix];
if (entry & singleton) return (prefix << leaf_bits) | (entry ^ singleton);
const auto &leaf = leaves[entry];
auto preferred = (value & leaf_mask) >> 6;
for (unsigned distance = 0; distance < 8; ++distance) {
auto i = preferred ^ distance;
if (leaf.words[i]) return (prefix << leaf_bits) | (i << 6) | xor_select_detail::argmin(leaf.words[i], value & 63);
}
std::unreachable();
}
std::uint32_t argmax_xor(std::uint32_t value) const { return argmin_xor(~value); }
std::uint32_t min_xor(std::uint32_t value) const { return argmin_xor(value) ^ value; }
std::uint32_t max_xor(std::uint32_t value) const { return argmax_xor(value) ^ value; }
void clear() {
std::fill(directory.begin(), directory.end(), 0);
occupied.clear(); leaves.clear();
free_leaf = all_xor = 0; count = 0;
}
void swap(XorSet &other) noexcept {
directory.swap(other.directory); occupied.swap(other.occupied); leaves.swap(other.leaves);
std::swap(free_leaf, other.free_leaf); std::swap(all_xor, other.all_xor); std::swap(count, other.count);
}
std::size_t memory_usage() const { return directory.capacity() * sizeof(std::uint32_t) + occupied.memory_usage() + leaves.capacity() * sizeof(Leaf); }
};