Skip to content

rectangle_affine_kd_tree.hpp

SECTIONData Structure INCLUDEnoya/rectangle_affine_kd_tree.hpp

Offline dynamic weighted point set using a balanced two-dimensional KD-tree. Every node stores its coordinate bounding box, active-point count, weight sum, and a lazy affine tag. A rectangle operation stops at a fully covered box, while points that will be inserted later start inactive, so earlier affine updates neither count nor change them.

Verified by dynamic_point_set_rectangle_affine_rectangle_sum.

在二维点集上对矩形内点权施加仿射变换并查询矩形和;适合动态二维范围更新。

Implementation

View on GitHub

#ifndef NOYA_RECTANGLE_AFFINE_KD_TREE_HPP
#define NOYA_RECTANGLE_AFFINE_KD_TREE_HPP 1

/// @complexity Time: Expected O(n log n) build, O(log n) point assignment,
/// and O(sqrt(n)) per rectangle sum or affine update; rectangle operations are
/// O(n) in the worst case. Space: O(n).

#include <algorithm>
#include <cassert>
#include <numeric>
#include <utility>
#include <vector>

namespace noya {

/// @brief Offline dynamic weighted point set using a balanced two-dimensional
/// KD-tree. Every node stores its coordinate bounding box, active-point count,
/// weight sum, and a lazy affine tag. A rectangle operation stops at a fully
/// covered box, while points that will be inserted later start inactive, so
/// earlier affine updates neither count nor change them.
template <class Coordinate, class Value> class rectangle_affine_kd_tree {
public:
  using point_type = std::pair<Coordinate, Coordinate>;

  rectangle_affine_kd_tree() = default;

  rectangle_affine_kd_tree(const std::vector<point_type> &points,
                           const std::vector<Value> &values,
                           const std::vector<bool> &active) {
    build(points, values, active);
  }

  /// @brief Build from every coordinate that can ever be inserted. Inactive
  /// points ignore rectangle operations until point_set activates them.
  void build(const std::vector<point_type> &points,
             const std::vector<Value> &values,
             const std::vector<bool> &active) {
    assert(points.size() == values.size());
    assert(points.size() == active.size());
    points_ = points;
    rank_.assign(points.size(), 0);
    tree_.assign(std::max<std::size_t>(1, points.size() * 4), node{});
    std::vector<int> order(points.size());
    std::iota(order.begin(), order.end(), 0);
    if (!order.empty()) {
      build_at(1, 0, int(order.size()), 0, order, values, active);
    }
  }

  /// @brief Return the number of offline coordinate instances.
  int size() const { return int(points_.size()); }

  /// @brief Assign one point's weight and mark it active.
  void point_set(int index, const Value &value) {
    assert(0 <= index && index < size());
    point_set_at(1, 0, size(), rank_[index], value);
  }

  /// @brief Sum active weights in [left, right) x [bottom, top).
  Value rectangle_sum(const Coordinate &left, const Coordinate &right,
                      const Coordinate &bottom, const Coordinate &top) {
    if (size() == 0 || !(left < right) || !(bottom < top)) {
      return Value(0);
    }
    return rectangle_sum_at(1, 0, size(), left, right, bottom, top);
  }

  /// @brief Replace every active weight w in the half-open rectangle by
  /// multiplier * w + addend.
  void rectangle_apply(const Coordinate &left, const Coordinate &right,
                       const Coordinate &bottom, const Coordinate &top,
                       const Value &multiplier, const Value &addend) {
    if (size() == 0 || !(left < right) || !(bottom < top)) {
      return;
    }
    rectangle_apply_at(1, 0, size(), left, right, bottom, top, multiplier,
                       addend);
  }

private:
  struct node {
    Coordinate min_x{};
    Coordinate max_x{};
    Coordinate min_y{};
    Coordinate max_y{};
    Value sum = Value(0);
    int active_count = 0;
    Value lazy_multiplier = Value(1);
    Value lazy_addend = Value(0);
    bool has_lazy = false;
  };

  std::vector<point_type> points_;
  std::vector<int> rank_;
  std::vector<node> tree_;

  void build_at(int id, int left, int right, int dimension,
                std::vector<int> &order, const std::vector<Value> &values,
                const std::vector<bool> &active) {
    if (left + 1 == right) {
      int index = order[left];
      rank_[index] = left;
      const auto &[x, y] = points_[index];
      tree_[id].min_x = tree_[id].max_x = x;
      tree_[id].min_y = tree_[id].max_y = y;
      tree_[id].active_count = active[index] ? 1 : 0;
      tree_[id].sum = active[index] ? values[index] : Value(0);
      return;
    }
    int middle = (left + right) / 2;
    auto compare = [&](int first, int second) {
      const auto &a = points_[first];
      const auto &b = points_[second];
      if (dimension == 0) {
        if (a.first != b.first) {
          return a.first < b.first;
        }
        if (a.second != b.second) {
          return a.second < b.second;
        }
      } else {
        if (a.second != b.second) {
          return a.second < b.second;
        }
        if (a.first != b.first) {
          return a.first < b.first;
        }
      }
      return first < second;
    };
    std::nth_element(order.begin() + left, order.begin() + middle,
                     order.begin() + right, compare);
    build_at(id * 2, left, middle, dimension ^ 1, order, values, active);
    build_at(id * 2 + 1, middle, right, dimension ^ 1, order, values, active);
    pull(id);
  }

  void pull(int id) {
    const node &first = tree_[id * 2];
    const node &second = tree_[id * 2 + 1];
    tree_[id].min_x = std::min(first.min_x, second.min_x);
    tree_[id].max_x = std::max(first.max_x, second.max_x);
    tree_[id].min_y = std::min(first.min_y, second.min_y);
    tree_[id].max_y = std::max(first.max_y, second.max_y);
    tree_[id].sum = first.sum + second.sum;
    tree_[id].active_count = first.active_count + second.active_count;
  }

  void apply_node(int id, const Value &multiplier, const Value &addend) {
    node &current = tree_[id];
    current.sum = multiplier * current.sum +
                  addend * Value(current.active_count);
    if (current.has_lazy) {
      current.lazy_multiplier = multiplier * current.lazy_multiplier;
      current.lazy_addend = multiplier * current.lazy_addend + addend;
    } else {
      current.lazy_multiplier = multiplier;
      current.lazy_addend = addend;
      current.has_lazy = true;
    }
  }

  void push(int id) {
    node &current = tree_[id];
    if (!current.has_lazy) {
      return;
    }
    apply_node(id * 2, current.lazy_multiplier, current.lazy_addend);
    apply_node(id * 2 + 1, current.lazy_multiplier, current.lazy_addend);
    current.lazy_multiplier = Value(1);
    current.lazy_addend = Value(0);
    current.has_lazy = false;
  }

  static bool disjoint(const node &current, const Coordinate &left,
                       const Coordinate &right, const Coordinate &bottom,
                       const Coordinate &top) {
    return current.max_x < left || !(current.min_x < right) ||
           current.max_y < bottom || !(current.min_y < top);
  }

  static bool contained(const node &current, const Coordinate &left,
                        const Coordinate &right, const Coordinate &bottom,
                        const Coordinate &top) {
    return !(current.min_x < left) && current.max_x < right &&
           !(current.min_y < bottom) && current.max_y < top;
  }

  void point_set_at(int id, int left, int right, int position,
                    const Value &value) {
    if (left + 1 == right) {
      tree_[id].sum = value;
      tree_[id].active_count = 1;
      tree_[id].lazy_multiplier = Value(1);
      tree_[id].lazy_addend = Value(0);
      tree_[id].has_lazy = false;
      return;
    }
    push(id);
    int middle = (left + right) / 2;
    if (position < middle) {
      point_set_at(id * 2, left, middle, position, value);
    } else {
      point_set_at(id * 2 + 1, middle, right, position, value);
    }
    pull(id);
  }

  Value rectangle_sum_at(int id, int left_index, int right_index,
                         const Coordinate &left, const Coordinate &right,
                         const Coordinate &bottom, const Coordinate &top) {
    const node &current = tree_[id];
    if (disjoint(current, left, right, bottom, top)) {
      return Value(0);
    }
    if (contained(current, left, right, bottom, top)) {
      return current.sum;
    }
    push(id);
    int middle = (left_index + right_index) / 2;
    return rectangle_sum_at(id * 2, left_index, middle, left, right, bottom,
                            top) +
           rectangle_sum_at(id * 2 + 1, middle, right_index, left, right,
                            bottom, top);
  }

  void rectangle_apply_at(int id, int left_index, int right_index,
                          const Coordinate &left, const Coordinate &right,
                          const Coordinate &bottom, const Coordinate &top,
                          const Value &multiplier, const Value &addend) {
    const node &current = tree_[id];
    if (disjoint(current, left, right, bottom, top)) {
      return;
    }
    if (contained(current, left, right, bottom, top)) {
      apply_node(id, multiplier, addend);
      return;
    }
    push(id);
    int middle = (left_index + right_index) / 2;
    rectangle_apply_at(id * 2, left_index, middle, left, right, bottom, top,
                       multiplier, addend);
    rectangle_apply_at(id * 2 + 1, middle, right_index, left, right, bottom,
                       top, multiplier, addend);
    pull(id);
  }
};

} // namespace noya

#endif // NOYA_RECTANGLE_AFFINE_KD_TREE_HPP
#include <algorithm>
#include <cassert>
#include <numeric>
#include <utility>
#include <vector>

/// @complexity Time: Expected O(n log n) build, O(log n) point assignment,
/// and O(sqrt(n)) per rectangle sum or affine update; rectangle operations are
/// O(n) in the worst case. Space: O(n).

namespace noya {

/// @brief Offline dynamic weighted point set using a balanced two-dimensional
/// KD-tree. Every node stores its coordinate bounding box, active-point count,
/// weight sum, and a lazy affine tag. A rectangle operation stops at a fully
/// covered box, while points that will be inserted later start inactive, so
/// earlier affine updates neither count nor change them.
template <class Coordinate, class Value> class rectangle_affine_kd_tree {
public:
  using point_type = std::pair<Coordinate, Coordinate>;

  rectangle_affine_kd_tree() = default;

  rectangle_affine_kd_tree(const std::vector<point_type> &points,
                           const std::vector<Value> &values,
                           const std::vector<bool> &active) {
    build(points, values, active);
  }

  /// @brief Build from every coordinate that can ever be inserted. Inactive
  /// points ignore rectangle operations until point_set activates them.
  void build(const std::vector<point_type> &points,
             const std::vector<Value> &values,
             const std::vector<bool> &active) {
    assert(points.size() == values.size());
    assert(points.size() == active.size());
    points_ = points;
    rank_.assign(points.size(), 0);
    tree_.assign(std::max<std::size_t>(1, points.size() * 4), node{});
    std::vector<int> order(points.size());
    std::iota(order.begin(), order.end(), 0);
    if (!order.empty()) {
      build_at(1, 0, int(order.size()), 0, order, values, active);
    }
  }

  /// @brief Return the number of offline coordinate instances.
  int size() const { return int(points_.size()); }

  /// @brief Assign one point's weight and mark it active.
  void point_set(int index, const Value &value) {
    assert(0 <= index && index < size());
    point_set_at(1, 0, size(), rank_[index], value);
  }

  /// @brief Sum active weights in [left, right) x [bottom, top).
  Value rectangle_sum(const Coordinate &left, const Coordinate &right,
                      const Coordinate &bottom, const Coordinate &top) {
    if (size() == 0 || !(left < right) || !(bottom < top)) {
      return Value(0);
    }
    return rectangle_sum_at(1, 0, size(), left, right, bottom, top);
  }

  /// @brief Replace every active weight w in the half-open rectangle by
  /// multiplier * w + addend.
  void rectangle_apply(const Coordinate &left, const Coordinate &right,
                       const Coordinate &bottom, const Coordinate &top,
                       const Value &multiplier, const Value &addend) {
    if (size() == 0 || !(left < right) || !(bottom < top)) {
      return;
    }
    rectangle_apply_at(1, 0, size(), left, right, bottom, top, multiplier,
                       addend);
  }

private:
  struct node {
    Coordinate min_x{};
    Coordinate max_x{};
    Coordinate min_y{};
    Coordinate max_y{};
    Value sum = Value(0);
    int active_count = 0;
    Value lazy_multiplier = Value(1);
    Value lazy_addend = Value(0);
    bool has_lazy = false;
  };

  std::vector<point_type> points_;
  std::vector<int> rank_;
  std::vector<node> tree_;

  void build_at(int id, int left, int right, int dimension,
                std::vector<int> &order, const std::vector<Value> &values,
                const std::vector<bool> &active) {
    if (left + 1 == right) {
      int index = order[left];
      rank_[index] = left;
      const auto &[x, y] = points_[index];
      tree_[id].min_x = tree_[id].max_x = x;
      tree_[id].min_y = tree_[id].max_y = y;
      tree_[id].active_count = active[index] ? 1 : 0;
      tree_[id].sum = active[index] ? values[index] : Value(0);
      return;
    }
    int middle = (left + right) / 2;
    auto compare = [&](int first, int second) {
      const auto &a = points_[first];
      const auto &b = points_[second];
      if (dimension == 0) {
        if (a.first != b.first) {
          return a.first < b.first;
        }
        if (a.second != b.second) {
          return a.second < b.second;
        }
      } else {
        if (a.second != b.second) {
          return a.second < b.second;
        }
        if (a.first != b.first) {
          return a.first < b.first;
        }
      }
      return first < second;
    };
    std::nth_element(order.begin() + left, order.begin() + middle,
                     order.begin() + right, compare);
    build_at(id * 2, left, middle, dimension ^ 1, order, values, active);
    build_at(id * 2 + 1, middle, right, dimension ^ 1, order, values, active);
    pull(id);
  }

  void pull(int id) {
    const node &first = tree_[id * 2];
    const node &second = tree_[id * 2 + 1];
    tree_[id].min_x = std::min(first.min_x, second.min_x);
    tree_[id].max_x = std::max(first.max_x, second.max_x);
    tree_[id].min_y = std::min(first.min_y, second.min_y);
    tree_[id].max_y = std::max(first.max_y, second.max_y);
    tree_[id].sum = first.sum + second.sum;
    tree_[id].active_count = first.active_count + second.active_count;
  }

  void apply_node(int id, const Value &multiplier, const Value &addend) {
    node &current = tree_[id];
    current.sum = multiplier * current.sum +
                  addend * Value(current.active_count);
    if (current.has_lazy) {
      current.lazy_multiplier = multiplier * current.lazy_multiplier;
      current.lazy_addend = multiplier * current.lazy_addend + addend;
    } else {
      current.lazy_multiplier = multiplier;
      current.lazy_addend = addend;
      current.has_lazy = true;
    }
  }

  void push(int id) {
    node &current = tree_[id];
    if (!current.has_lazy) {
      return;
    }
    apply_node(id * 2, current.lazy_multiplier, current.lazy_addend);
    apply_node(id * 2 + 1, current.lazy_multiplier, current.lazy_addend);
    current.lazy_multiplier = Value(1);
    current.lazy_addend = Value(0);
    current.has_lazy = false;
  }

  static bool disjoint(const node &current, const Coordinate &left,
                       const Coordinate &right, const Coordinate &bottom,
                       const Coordinate &top) {
    return current.max_x < left || !(current.min_x < right) ||
           current.max_y < bottom || !(current.min_y < top);
  }

  static bool contained(const node &current, const Coordinate &left,
                        const Coordinate &right, const Coordinate &bottom,
                        const Coordinate &top) {
    return !(current.min_x < left) && current.max_x < right &&
           !(current.min_y < bottom) && current.max_y < top;
  }

  void point_set_at(int id, int left, int right, int position,
                    const Value &value) {
    if (left + 1 == right) {
      tree_[id].sum = value;
      tree_[id].active_count = 1;
      tree_[id].lazy_multiplier = Value(1);
      tree_[id].lazy_addend = Value(0);
      tree_[id].has_lazy = false;
      return;
    }
    push(id);
    int middle = (left + right) / 2;
    if (position < middle) {
      point_set_at(id * 2, left, middle, position, value);
    } else {
      point_set_at(id * 2 + 1, middle, right, position, value);
    }
    pull(id);
  }

  Value rectangle_sum_at(int id, int left_index, int right_index,
                         const Coordinate &left, const Coordinate &right,
                         const Coordinate &bottom, const Coordinate &top) {
    const node &current = tree_[id];
    if (disjoint(current, left, right, bottom, top)) {
      return Value(0);
    }
    if (contained(current, left, right, bottom, top)) {
      return current.sum;
    }
    push(id);
    int middle = (left_index + right_index) / 2;
    return rectangle_sum_at(id * 2, left_index, middle, left, right, bottom,
                            top) +
           rectangle_sum_at(id * 2 + 1, middle, right_index, left, right,
                            bottom, top);
  }

  void rectangle_apply_at(int id, int left_index, int right_index,
                          const Coordinate &left, const Coordinate &right,
                          const Coordinate &bottom, const Coordinate &top,
                          const Value &multiplier, const Value &addend) {
    const node &current = tree_[id];
    if (disjoint(current, left, right, bottom, top)) {
      return;
    }
    if (contained(current, left, right, bottom, top)) {
      apply_node(id, multiplier, addend);
      return;
    }
    push(id);
    int middle = (left_index + right_index) / 2;
    rectangle_apply_at(id * 2, left_index, middle, left, right, bottom, top,
                       multiplier, addend);
    rectangle_apply_at(id * 2 + 1, middle, right_index, left, right, bottom,
                       top, multiplier, addend);
    pull(id);
  }
};

} // namespace noya