Skip to content

static_range_xor_basis.hpp

SECTIONData Structure INCLUDEnoya/static_range_xor_basis.hpp

Prefix-persistent linear bases for maximum xor and membership in a static subarray, with O(B) queries and O(nB) preprocessing.

为静态数组区间建立线性基,回答子集异或最大值、可达性或秩等查询。

Implementation

View on GitHub

#ifndef NOYA_STATIC_RANGE_XOR_BASIS_HPP
#define NOYA_STATIC_RANGE_XOR_BASIS_HPP 1

/// @complexity Time: O(nB) build and O(B) query for B-bit values.
/// Space: O(nB).

#include "noya/xor_basis.hpp"

#include <array>
#include <cassert>
#include <limits>
#include <type_traits>
#include <utility>
#include <vector>

namespace noya {

/// @brief Prefix-persistent linear bases for maximum xor and membership in a
/// static subarray, with O(B) queries and O(nB) preprocessing.
template <class UInt, int bits = std::numeric_limits<UInt>::digits>
struct static_range_xor_basis {
  static_assert(std::is_integral_v<UInt> && std::is_unsigned_v<UInt>);
  static_assert(0 < bits && bits <= std::numeric_limits<UInt>::digits);

  struct entry {
    UInt value = 0;
    int position = -1;
  };

  int n = 0;
  std::vector<std::array<entry, bits>> prefixes;

  static_range_xor_basis() = default;
  explicit static_range_xor_basis(const std::vector<UInt> &values) {
    build(values);
  }

  void build(const std::vector<UInt> &values) {
    n = int(values.size());
    prefixes.assign(n + 1, {});
    for (int position = 0; position < n; position++) {
      prefixes[position + 1] = prefixes[position];
      insert(prefixes[position + 1], values[position], position);
    }
  }

  /// @brief Maximize seed xor x over subset x of values[left, right).
  UInt max_xor(int left, int right, UInt seed = 0) const {
    check_range(left, right);
    const auto &basis = prefixes[right];
    for (int bit = bits - 1; bit >= 0; bit--) {
      if (basis[bit].position >= left &&
          (seed ^ basis[bit].value) > seed) {
        seed ^= basis[bit].value;
      }
    }
    return seed;
  }

  /// @brief Minimize seed xor x over subset x of values[left, right).
  UInt min_xor(int left, int right, UInt seed) const {
    check_range(left, right);
    const auto &basis = prefixes[right];
    for (int bit = bits - 1; bit >= 0; bit--) {
      if (basis[bit].position >= left &&
          (seed ^ basis[bit].value) < seed) {
        seed ^= basis[bit].value;
      }
    }
    return seed;
  }

  /// @brief Return whether value is an xor of a subset of values[left,right).
  bool contains(int left, int right, UInt value) const {
    check_range(left, right);
    const auto &basis = prefixes[right];
    for (int bit = bits - 1; bit >= 0; bit--) {
      if ((value >> bit) & UInt(1)) {
        if (basis[bit].position < left) {
          return false;
        }
        value ^= basis[bit].value;
      }
    }
    return true;
  }

  int dimension(int left, int right) const {
    check_range(left, right);
    int result = 0;
    for (const entry &item : prefixes[right]) {
      result += item.position >= left;
    }
    return result;
  }

  /// @brief Materialize an ordinary xor basis of a subarray.
  xor_basis<UInt, bits> range_basis(int left, int right) const {
    check_range(left, right);
    xor_basis<UInt, bits> result;
    for (const entry &item : prefixes[right]) {
      if (item.position >= left) {
        result.insert(item.value);
      }
    }
    return result;
  }

private:
  static void insert(std::array<entry, bits> &basis, UInt value,
                     int position) {
    for (int bit = bits - 1; bit >= 0 && value != 0; bit--) {
      if (((value >> bit) & UInt(1)) == 0) {
        continue;
      }
      if (basis[bit].value == 0) {
        basis[bit] = {value, position};
        return;
      }
      if (position > basis[bit].position) {
        std::swap(value, basis[bit].value);
        std::swap(position, basis[bit].position);
      }
      value ^= basis[bit].value;
    }
  }

  void check_range(int left, int right) const {
    assert(0 <= left && left <= right && right <= n);
  }
};

} // namespace noya

#endif // NOYA_STATIC_RANGE_XOR_BASIS_HPP
#include <array>
#include <cassert>
#include <cstdint>
#include <limits>
#include <type_traits>
#include <utility>
#include <vector>

/// @complexity Time: O(nB) build and O(B) query for B-bit values.
/// Space: O(nB).

/// @complexity Time: O(B^2) reduced insertion and O(B) membership/min/max query.
/// Space: O(B).

namespace noya {

/// @brief Reduced linear basis over GF(2) for an unsigned integer type.
template <class UInt, int bits = std::numeric_limits<UInt>::digits>
struct xor_basis {
  static_assert(std::is_integral_v<UInt> && std::is_unsigned_v<UInt>);
  static_assert(0 < bits && bits <= std::numeric_limits<UInt>::digits);

  std::array<UInt, bits> basis{};
  int dimension = 0;

  /// @brief Insert a value; return false if it is already in the span.
  bool insert(UInt value) {
    for (int bit = bits - 1; bit >= 0; bit--) {
      if (((value >> bit) & UInt(1)) == 0) {
        continue;
      }
      if (basis[bit] != 0) {
        value ^= basis[bit];
        continue;
      }
      basis[bit] = value;
      for (int lower = 0; lower < bit; lower++) {
        if ((basis[bit] >> lower) & UInt(1)) {
          basis[bit] ^= basis[lower];
        }
      }
      for (int higher = bit + 1; higher < bits; higher++) {
        if ((basis[higher] >> bit) & UInt(1)) {
          basis[higher] ^= basis[bit];
        }
      }
      dimension++;
      return true;
    }
    return false;
  }

  /// @brief Return whether value belongs to the represented xor span.
  bool contains(UInt value) const {
    for (int bit = bits - 1; bit >= 0; bit--) {
      if ((value >> bit) & UInt(1)) {
        value ^= basis[bit];
      }
    }
    return value == 0;
  }

  /// @brief Maximize seed xor x over all represented values x.
  UInt max_xor(UInt seed = 0) const {
    for (int bit = bits - 1; bit >= 0; bit--) {
      if ((seed ^ basis[bit]) > seed) {
        seed ^= basis[bit];
      }
    }
    return seed;
  }

  /// @brief Minimize seed xor x over all represented values x.
  UInt min_xor(UInt seed) const {
    for (int bit = bits - 1; bit >= 0; bit--) {
      if ((seed ^ basis[bit]) < seed) {
        seed ^= basis[bit];
      }
    }
    return seed;
  }

  /// @brief Return the k-th smallest distinct value in the span, including
  /// zero.
  UInt kth_smallest(std::uint64_t k) const {
    if (dimension < 64) {
      assert(k < (std::uint64_t(1) << dimension));
    }
    UInt result = 0;
    int index = 0;
    for (int bit = 0; bit < bits; bit++) {
      if (basis[bit] != 0) {
        if ((k >> index) & 1) {
          result ^= basis[bit];
        }
        index++;
      }
    }
    return result;
  }

  /// @brief Insert every basis vector from another span.
  void merge(const xor_basis &other) {
    for (UInt value : other.basis) {
      if (value != 0) {
        insert(value);
      }
    }
  }
};

/// @brief Return a basis of the intersection of two xor spans.  A homogeneous
/// system is built for U*a = V*b, with one equation per value bit. Gaussian
/// elimination produces a nullspace basis; projecting its U-coordinates gives
/// an independent basis of the intersection because both input lists are
/// required to be independent.
template <class UInt, int bits = std::numeric_limits<UInt>::digits>
std::vector<UInt>
xor_space_intersection(const std::vector<UInt> &first,
                       const std::vector<UInt> &second) {
  static_assert(std::is_integral_v<UInt> && std::is_unsigned_v<UInt>);
  static_assert(0 < bits && bits <= std::numeric_limits<UInt>::digits);
  const int left_size = int(first.size());
  const int variable_count = left_size + int(second.size());
  assert(variable_count <= 64);
  xor_basis<UInt, bits> left_check;
  xor_basis<UInt, bits> right_check;
  for (UInt value : first) {
    assert(left_check.insert(value));
  }
  for (UInt value : second) {
    assert(right_check.insert(value));
  }

  std::array<std::uint64_t, bits> equations{};
  for (int variable = 0; variable < variable_count; variable++) {
    UInt value = variable < left_size ? first[variable]
                                      : second[variable - left_size];
    for (int bit = 0; bit < bits; bit++) {
      if ((value >> bit) & UInt(1)) {
        equations[bit] |= std::uint64_t(1) << variable;
      }
    }
  }

  std::array<int, 64> pivot_row{};
  pivot_row.fill(-1);
  int rank = 0;
  for (int column = 0; column < variable_count; column++) {
    int pivot = rank;
    while (pivot < bits && ((equations[pivot] >> column) & 1) == 0) {
      pivot++;
    }
    if (pivot == bits) {
      continue;
    }
    std::swap(equations[rank], equations[pivot]);
    pivot_row[column] = rank;
    for (int row = 0; row < bits; row++) {
      if (row != rank && ((equations[row] >> column) & 1)) {
        equations[row] ^= equations[rank];
      }
    }
    rank++;
  }

  std::vector<UInt> result;
  for (int free_column = 0; free_column < variable_count; free_column++) {
    if (pivot_row[free_column] != -1) {
      continue;
    }
    std::uint64_t coefficients = std::uint64_t(1) << free_column;
    for (int column = 0; column < variable_count; column++) {
      int row = pivot_row[column];
      if (row != -1 && ((equations[row] >> free_column) & 1)) {
        coefficients |= std::uint64_t(1) << column;
      }
    }
    UInt value = 0;
    for (int index = 0; index < left_size; index++) {
      if ((coefficients >> index) & 1) {
        value ^= first[index];
      }
    }
    result.push_back(value);
  }
  return result;
}

} // namespace noya

namespace noya {

/// @brief Prefix-persistent linear bases for maximum xor and membership in a
/// static subarray, with O(B) queries and O(nB) preprocessing.
template <class UInt, int bits = std::numeric_limits<UInt>::digits>
struct static_range_xor_basis {
  static_assert(std::is_integral_v<UInt> && std::is_unsigned_v<UInt>);
  static_assert(0 < bits && bits <= std::numeric_limits<UInt>::digits);

  struct entry {
    UInt value = 0;
    int position = -1;
  };

  int n = 0;
  std::vector<std::array<entry, bits>> prefixes;

  static_range_xor_basis() = default;
  explicit static_range_xor_basis(const std::vector<UInt> &values) {
    build(values);
  }

  void build(const std::vector<UInt> &values) {
    n = int(values.size());
    prefixes.assign(n + 1, {});
    for (int position = 0; position < n; position++) {
      prefixes[position + 1] = prefixes[position];
      insert(prefixes[position + 1], values[position], position);
    }
  }

  /// @brief Maximize seed xor x over subset x of values[left, right).
  UInt max_xor(int left, int right, UInt seed = 0) const {
    check_range(left, right);
    const auto &basis = prefixes[right];
    for (int bit = bits - 1; bit >= 0; bit--) {
      if (basis[bit].position >= left &&
          (seed ^ basis[bit].value) > seed) {
        seed ^= basis[bit].value;
      }
    }
    return seed;
  }

  /// @brief Minimize seed xor x over subset x of values[left, right).
  UInt min_xor(int left, int right, UInt seed) const {
    check_range(left, right);
    const auto &basis = prefixes[right];
    for (int bit = bits - 1; bit >= 0; bit--) {
      if (basis[bit].position >= left &&
          (seed ^ basis[bit].value) < seed) {
        seed ^= basis[bit].value;
      }
    }
    return seed;
  }

  /// @brief Return whether value is an xor of a subset of values[left,right).
  bool contains(int left, int right, UInt value) const {
    check_range(left, right);
    const auto &basis = prefixes[right];
    for (int bit = bits - 1; bit >= 0; bit--) {
      if ((value >> bit) & UInt(1)) {
        if (basis[bit].position < left) {
          return false;
        }
        value ^= basis[bit].value;
      }
    }
    return true;
  }

  int dimension(int left, int right) const {
    check_range(left, right);
    int result = 0;
    for (const entry &item : prefixes[right]) {
      result += item.position >= left;
    }
    return result;
  }

  /// @brief Materialize an ordinary xor basis of a subarray.
  xor_basis<UInt, bits> range_basis(int left, int right) const {
    check_range(left, right);
    xor_basis<UInt, bits> result;
    for (const entry &item : prefixes[right]) {
      if (item.position >= left) {
        result.insert(item.value);
      }
    }
    return result;
  }

private:
  static void insert(std::array<entry, bits> &basis, UInt value,
                     int position) {
    for (int bit = bits - 1; bit >= 0 && value != 0; bit--) {
      if (((value >> bit) & UInt(1)) == 0) {
        continue;
      }
      if (basis[bit].value == 0) {
        basis[bit] = {value, position};
        return;
      }
      if (position > basis[bit].position) {
        std::swap(value, basis[bit].value);
        std::swap(position, basis[bit].position);
      }
      value ^= basis[bit].value;
    }
  }

  void check_range(int left, int right) const {
    assert(0 <= left && left <= right && right <= n);
  }
};

} // namespace noya