Skip to content

binary_trie.hpp

SECTIONData Structure INCLUDEnoya/binary_trie.hpp

维护整数的二进制字典树,支持插入删除、异或最值和按异或序第 \(k\) 小;适合动态 XOR 查询。

Complexity: Time: O(B) per update or query for B-bit keys. Space: O(NB) nodes after N distinct insertion paths.

AC 记录:ordered_set, set_xor_min

跳到代码 · GitHub ↗

Implementation

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

/// @complexity Time: O(B) per update or query for B-bit keys.
/// Space: O(NB) nodes after N distinct insertion paths.

#include <array>
#include <cassert>
#include <cstdint>
#include <limits>
#include <vector>

namespace noya {

/// @brief Multiset of fixed-width unsigned integers with xor-order queries.
template <int B, class UInt = std::uint64_t> struct binary_trie {
  static_assert(B > 0);
  static_assert(B <= std::numeric_limits<UInt>::digits);

  struct node {
    std::array<int, 2> ch = {-1, -1};
    int num = 0;
  };

  std::vector<node> tr = {node{}};

  int size() const { return tr[0].num; }
  bool empty() const { return size() == 0; }

  /// @brief Insert one occurrence of value in O(B).
  void insert(UInt val) {
    assert(in_range(val));
    int cur = 0;
    tr[cur].num++;
    for (int bit = B - 1; bit >= 0; bit--) {
      int dir = int((val >> bit) & UInt(1));
      if (tr[cur].ch[dir] == -1) {
        tr[cur].ch[dir] = int(tr.size());
        tr.push_back(node{});
      }
      cur = tr[cur].ch[dir];
      tr[cur].num++;
    }
  }

  /// @brief Erase one occurrence of value, returning whether it existed.
  bool erase(UInt val) {
    assert(in_range(val));
    if (count(val) == 0) {
      return false;
    }
    int cur = 0;
    tr[cur].num--;
    for (int bit = B - 1; bit >= 0; bit--) {
      int dir = int((val >> bit) & UInt(1));
      cur = tr[cur].ch[dir];
      tr[cur].num--;
    }
    return true;
  }

  int count(UInt val) const {
    assert(in_range(val));
    int cur = 0;
    for (int bit = B - 1; bit >= 0; bit--) {
      int dir = int((val >> bit) & UInt(1));
      cur = tr[cur].ch[dir];
      if (cur == -1 || tr[cur].num == 0) {
        return 0;
      }
    }
    return tr[cur].num;
  }

  /// @brief Return the k-th smallest value after xor by tag.
  UInt kth_xor(int k, UInt tag = 0) const {
    assert(0 <= k && k < size());
    assert(in_range(tag));
    int cur = 0;
    UInt res = 0;
    for (int bit = B - 1; bit >= 0; bit--) {
      int mb = int((tag >> bit) & UInt(1));
      int pre = tr[cur].ch[mb];
      int cnt = pre == -1 ? 0 : tr[pre].num;
      if (k < cnt) {
        cur = pre;
      } else {
        k -= cnt;
        cur = tr[cur].ch[mb ^ 1];
        res |= UInt(1) << bit;
      }
      assert(cur != -1);
    }
    return res;
  }

  UInt min_xor(UInt tag = 0) const { return kth_xor(0, tag); }
  UInt max_xor(UInt tag = 0) const {
    return kth_xor(size() - 1, tag);
  }

  /// @brief Count values x satisfying (x xor tag) < hi.
  int count_less(UInt hi, UInt tag = 0) const {
    assert(in_range(tag));
    if constexpr (B < std::numeric_limits<UInt>::digits) {
      if ((hi >> B) != 0) {
        return size();
      }
    }
    int cur = 0;
    int res = 0;
    for (int bit = B - 1; bit >= 0 && cur != -1; bit--) {
      int mb = int((tag >> bit) & UInt(1));
      int ub = int((hi >> bit) & UInt(1));
      if (ub != 0) {
        int sm = tr[cur].ch[mb];
        if (sm != -1) {
          res += tr[sm].num;
        }
        cur = tr[cur].ch[mb ^ 1];
      } else {
        cur = tr[cur].ch[mb];
      }
    }
    return res;
  }

private:
  static bool in_range(UInt val) {
    if constexpr (B == std::numeric_limits<UInt>::digits) {
      return true;
    } else {
      return (val >> B) == 0;
    }
  }
};

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

/// @complexity Time: O(B) per update or query for B-bit keys.
/// Space: O(NB) nodes after N distinct insertion paths.

#include <array>
#include <cassert>
#include <cstdint>
#include <limits>
#include <vector>

namespace noya {

/// @brief Multiset of fixed-width unsigned integers with xor-order queries.
template <int B, class UInt = std::uint64_t> struct binary_trie {
  static_assert(B > 0);
  static_assert(B <= std::numeric_limits<UInt>::digits);

  struct node {
    std::array<int, 2> ch = {-1, -1};
    int num = 0;
  };

  std::vector<node> tr = {node{}};

  int size() const { return tr[0].num; }
  bool empty() const { return size() == 0; }

  /// @brief Insert one occurrence of value in O(B).
  void insert(UInt val) {
    assert(in_range(val));
    int cur = 0;
    tr[cur].num++;
    for (int bit = B - 1; bit >= 0; bit--) {
      int dir = int((val >> bit) & UInt(1));
      if (tr[cur].ch[dir] == -1) {
        tr[cur].ch[dir] = int(tr.size());
        tr.push_back(node{});
      }
      cur = tr[cur].ch[dir];
      tr[cur].num++;
    }
  }

  /// @brief Erase one occurrence of value, returning whether it existed.
  bool erase(UInt val) {
    assert(in_range(val));
    if (count(val) == 0) {
      return false;
    }
    int cur = 0;
    tr[cur].num--;
    for (int bit = B - 1; bit >= 0; bit--) {
      int dir = int((val >> bit) & UInt(1));
      cur = tr[cur].ch[dir];
      tr[cur].num--;
    }
    return true;
  }

  int count(UInt val) const {
    assert(in_range(val));
    int cur = 0;
    for (int bit = B - 1; bit >= 0; bit--) {
      int dir = int((val >> bit) & UInt(1));
      cur = tr[cur].ch[dir];
      if (cur == -1 || tr[cur].num == 0) {
        return 0;
      }
    }
    return tr[cur].num;
  }

  /// @brief Return the k-th smallest value after xor by tag.
  UInt kth_xor(int k, UInt tag = 0) const {
    assert(0 <= k && k < size());
    assert(in_range(tag));
    int cur = 0;
    UInt res = 0;
    for (int bit = B - 1; bit >= 0; bit--) {
      int mb = int((tag >> bit) & UInt(1));
      int pre = tr[cur].ch[mb];
      int cnt = pre == -1 ? 0 : tr[pre].num;
      if (k < cnt) {
        cur = pre;
      } else {
        k -= cnt;
        cur = tr[cur].ch[mb ^ 1];
        res |= UInt(1) << bit;
      }
      assert(cur != -1);
    }
    return res;
  }

  UInt min_xor(UInt tag = 0) const { return kth_xor(0, tag); }
  UInt max_xor(UInt tag = 0) const {
    return kth_xor(size() - 1, tag);
  }

  /// @brief Count values x satisfying (x xor tag) < hi.
  int count_less(UInt hi, UInt tag = 0) const {
    assert(in_range(tag));
    if constexpr (B < std::numeric_limits<UInt>::digits) {
      if ((hi >> B) != 0) {
        return size();
      }
    }
    int cur = 0;
    int res = 0;
    for (int bit = B - 1; bit >= 0 && cur != -1; bit--) {
      int mb = int((tag >> bit) & UInt(1));
      int ub = int((hi >> bit) & UInt(1));
      if (ub != 0) {
        int sm = tr[cur].ch[mb];
        if (sm != -1) {
          res += tr[sm].num;
        }
        cur = tr[cur].ch[mb ^ 1];
      } else {
        cur = tr[cur].ch[mb];
      }
    }
    return res;
  }

private:
  static bool in_range(UInt val) {
    if constexpr (B == std::numeric_limits<UInt>::digits) {
      return true;
    } else {
      return (val >> B) == 0;
    }
  }
};

} // namespace noya

#endif // NOYA_BINARY_TRIE_HPP
#include <array>
#include <cassert>
#include <cstdint>
#include <limits>
#include <vector>

/// @complexity Time: O(B) per update or query for B-bit keys.
/// Space: O(NB) nodes after N distinct insertion paths.

namespace noya {

/// @brief Multiset of fixed-width unsigned integers with xor-order queries.
template <int B, class UInt = std::uint64_t> struct binary_trie {
  static_assert(B > 0);
  static_assert(B <= std::numeric_limits<UInt>::digits);

  struct node {
    std::array<int, 2> ch = {-1, -1};
    int num = 0;
  };

  std::vector<node> tr = {node{}};

  int size() const { return tr[0].num; }
  bool empty() const { return size() == 0; }

  /// @brief Insert one occurrence of value in O(B).
  void insert(UInt val) {
    assert(in_range(val));
    int cur = 0;
    tr[cur].num++;
    for (int bit = B - 1; bit >= 0; bit--) {
      int dir = int((val >> bit) & UInt(1));
      if (tr[cur].ch[dir] == -1) {
        tr[cur].ch[dir] = int(tr.size());
        tr.push_back(node{});
      }
      cur = tr[cur].ch[dir];
      tr[cur].num++;
    }
  }

  /// @brief Erase one occurrence of value, returning whether it existed.
  bool erase(UInt val) {
    assert(in_range(val));
    if (count(val) == 0) {
      return false;
    }
    int cur = 0;
    tr[cur].num--;
    for (int bit = B - 1; bit >= 0; bit--) {
      int dir = int((val >> bit) & UInt(1));
      cur = tr[cur].ch[dir];
      tr[cur].num--;
    }
    return true;
  }

  int count(UInt val) const {
    assert(in_range(val));
    int cur = 0;
    for (int bit = B - 1; bit >= 0; bit--) {
      int dir = int((val >> bit) & UInt(1));
      cur = tr[cur].ch[dir];
      if (cur == -1 || tr[cur].num == 0) {
        return 0;
      }
    }
    return tr[cur].num;
  }

  /// @brief Return the k-th smallest value after xor by tag.
  UInt kth_xor(int k, UInt tag = 0) const {
    assert(0 <= k && k < size());
    assert(in_range(tag));
    int cur = 0;
    UInt res = 0;
    for (int bit = B - 1; bit >= 0; bit--) {
      int mb = int((tag >> bit) & UInt(1));
      int pre = tr[cur].ch[mb];
      int cnt = pre == -1 ? 0 : tr[pre].num;
      if (k < cnt) {
        cur = pre;
      } else {
        k -= cnt;
        cur = tr[cur].ch[mb ^ 1];
        res |= UInt(1) << bit;
      }
      assert(cur != -1);
    }
    return res;
  }

  UInt min_xor(UInt tag = 0) const { return kth_xor(0, tag); }
  UInt max_xor(UInt tag = 0) const {
    return kth_xor(size() - 1, tag);
  }

  /// @brief Count values x satisfying (x xor tag) < hi.
  int count_less(UInt hi, UInt tag = 0) const {
    assert(in_range(tag));
    if constexpr (B < std::numeric_limits<UInt>::digits) {
      if ((hi >> B) != 0) {
        return size();
      }
    }
    int cur = 0;
    int res = 0;
    for (int bit = B - 1; bit >= 0 && cur != -1; bit--) {
      int mb = int((tag >> bit) & UInt(1));
      int ub = int((hi >> bit) & UInt(1));
      if (ub != 0) {
        int sm = tr[cur].ch[mb];
        if (sm != -1) {
          res += tr[sm].num;
        }
        cur = tr[cur].ch[mb ^ 1];
      } else {
        cur = tr[cur].ch[mb];
      }
    }
    return res;
  }

private:
  static bool in_range(UInt val) {
    if constexpr (B == std::numeric_limits<UInt>::digits) {
      return true;
    } else {
      return (val >> B) == 0;
    }
  }
};

} // namespace noya