競技プログラミング用 C++ ライブラリ
data_structure/offline_rectangle_sum.hpp · GitHub で見る
共通ページ
重み付きの点と矩形を登録し、各半開矩形に含まれる点の重みの和をまとめて求めます。
#include "lib/data_structure/offline_rectangle_sum.hpp"
OfflineRectangleSum<> rectangles;
rectangles.add_point(1, 2, 10);
rectangles.add_point(3, 4, -2);
int id = rectangles.add_query(0, 0, 3, 5);
auto answers = rectangles.solve(); // answers[id] == 10
| 操作 | 内容・返り値 | 計算量 |
|---|---|---|
OfflineRectangleSum<X=int64_t,Y=X,Sum=int64_t>(point_capacity=0,query_capacity=0) |
空の構造を作り、点数・矩形数の容量を予約 | \(O(1)\) |
add_point(x,y,weight) |
重み付きの点を追加。同じ座標も別々に数える | 償却 \(O(1)\) |
add_query(left,down,right,up) |
[left,right) × [down,up) を追加し、0 始まりの問い合わせ番号を返す |
償却 \(O(1)\) |
solve() const |
登録した全点を対象に、問い合わせ順の vector<Sum> を返す |
\(O((n+q)\log(n+q))\) |
幅または高さが 0 以下の矩形の答えは 0 です。solve は登録内容を変更せず、繰り返し呼べます。問い合わせの後に追加した点も、次の solve では全て対象になります。
座標型 X・Y にはコピーと < による厳密弱順序が必要です。デフォルト構築できない座標型にも対応します。Sum は 0 の構築・加算・減算に対応する型とし、重みと答えが表現できるように指定します。64 bit 以下の整数型では中間値のオーバーフローを避けるため、必要に応じて 128 bit 整数を使います。それ以外の型では中間の部分和も表現できる必要があります。点数・矩形数はそれぞれ INT_MAX 以下とします。
点数を \(n\)、矩形数を \(q\) として、solve は \(O((n+q)\log(n+q))\) 時間・\(O(n+q)\) 追加空間です。保持空間は \(O(n+q)\) です。比較・算術演算・コピーを定数時間とします。
#ifndef OFFLINE_RECTANGLE_SUM_HPP
#define OFFLINE_RECTANGLE_SUM_HPP
#include "../components/fenwick_sum.hpp"
#include "../components/radix_sort.hpp"
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <limits>
#include <type_traits>
#include <vector>
template <class X = std::int64_t, class Y = X, class Sum = std::int64_t>
class OfflineRectangleSum {
using Accum = std::conditional_t<std::is_integral_v<Sum> && sizeof(Sum) <= 8, __int128_t, Sum>;
struct Point { X x; Y y; Sum weight; int rank = 0; };
struct Query { X left, right; Y down, up; };
struct Event { X x; int down, up, query; bool subtract; };
std::vector<Point> points;
std::vector<Query> queries;
template <class A>
static void sweep(const std::vector<Point> &ordered, const std::vector<Event> &events, int size, std::vector<Accum> &answers) {
FenwickSum<A> tree(size);
std::size_t p = 0;
for (const auto &event : events) {
while (p < ordered.size() && ordered[p].x < event.x) {
tree.add(ordered[p].rank, A(ordered[p].weight));
++p;
}
auto value = tree.template prod_as<Accum>(event.down, event.up);
if (event.subtract) answers[event.query] -= value;
else answers[event.query] += value;
}
}
public:
explicit OfflineRectangleSum(int point_capacity = 0, int query_capacity = 0) {
assert(point_capacity >= 0 && query_capacity >= 0);
points.reserve(point_capacity); queries.reserve(query_capacity);
}
void add_point(const X &x, const Y &y, const Sum &weight) {
assert(points.size() < std::size_t(std::numeric_limits<int>::max()));
points.push_back({x, y, weight});
}
int add_query(const X &left, const Y &down, const X &right, const Y &up) {
assert(queries.size() < std::size_t(std::numeric_limits<int>::max()));
queries.push_back({left, right, down, up});
return int(queries.size() - 1);
}
std::vector<Sum> solve() const {
if (points.empty() || queries.empty()) return std::vector<Sum>(queries.size());
std::vector<Accum> answers(queries.size());
auto ordered = points;
radix_sort_detail::sort_by_key(ordered, [](const Point &p) -> const Y & { return p.y; });
std::vector<Y> ys;
ys.reserve(points.size());
for (auto &p : ordered) {
if (ys.empty() || ys.back() < p.y) ys.push_back(p.y);
p.rank = int(ys.size() - 1);
}
radix_sort_detail::sort_by_key(ordered, [](const Point &p) -> const X & { return p.x; });
std::vector<Event> events;
events.reserve(2 * queries.size());
for (int i = 0; i < int(queries.size()); ++i) {
const auto &q = queries[i];
if (!(q.left < q.right) || !(q.down < q.up)) continue;
int down = std::lower_bound(ys.begin(), ys.end(), q.down) - ys.begin();
int up = std::lower_bound(ys.begin(), ys.end(), q.up) - ys.begin();
if (down == up) continue;
events.push_back({q.left, down, up, i, true});
events.push_back({q.right, down, up, i, false});
}
radix_sort_detail::sort_by_key(events, [](const Event &e) -> const X & { return e.x; });
bool narrow = false;
if constexpr (std::is_integral_v<Sum> && sizeof(Sum) <= 8) {
Accum positive = 0, negative = 0;
for (const auto &point : points) {
Accum value(point.weight);
if (value > 0) positive += value;
else negative += value;
}
narrow = positive <= INT64_MAX && negative >= INT64_MIN;
}
if (narrow) {
if constexpr (std::is_integral_v<Sum> && sizeof(Sum) <= 8) sweep<std::int64_t>(ordered, events, int(ys.size()), answers);
} else sweep<Accum>(ordered, events, int(ys.size()), answers);
if constexpr (std::is_same_v<Accum, Sum>) return answers;
else return std::vector<Sum>(answers.begin(), answers.end());
}
};
#endif
#line 1 "data_structure/offline_rectangle_sum.hpp"
#line 1 "components/fenwick_sum.hpp"
#include <cassert>
#include <bit>
#include <limits>
#include <vector>
template <class T>
class FenwickSum {
std::vector<T> data;
public:
FenwickSum() = default;
explicit FenwickSum(int n) { assert(n >= 0); data.resize(n); }
explicit FenwickSum(const std::vector<T> &a) { build(a); }
void build(const std::vector<T> &a) {
assert(a.size() <= static_cast<unsigned>(std::numeric_limits<int>::max()));
data = a;
constexpr unsigned step = sizeof(T) <= 8 ? 2 : 1;
for (unsigned i = step; i <= data.size(); i += step) {
if constexpr (step == 2) data[i - 1] += data[i - 2];
unsigned p = i + (i & -i);
if (p <= data.size()) data[p - 1] += data[i - 1];
}
}
int size() const { return static_cast<int>(data.size()); }
void add(int p, const T &x) {
assert(0 <= p && p < size());
for (unsigned i = unsigned(p) + 1; i <= data.size(); i += i & -i) data[i - 1] += x;
}
void add_pair(int p, const T &x, int q, const T &y) {
assert(0 <= p && p < size() && 0 <= q && q < size());
unsigned a = unsigned(p) + 1, b = unsigned(q) + 1;
while (a != b && (a <= data.size() || b <= data.size())) {
if (a < b) data[a - 1] += x, a += a & -a;
else data[b - 1] += y, b += b & -b;
}
T both = x;
both += y;
for (; a <= data.size(); a += a & -a) data[a - 1] += both;
}
T prefix(int r) const {
assert(0 <= r && r <= size());
T result{};
for (; r; r &= r - 1) result += data[r - 1];
return result;
}
template <class Accum>
Accum prod_as(int l, int r) const {
assert(0 <= l && l <= r && r <= size());
Accum result{};
if (l == r) return result;
unsigned shared = unsigned(l) & (std::numeric_limits<unsigned>::max() << std::bit_width(unsigned(l ^ r)));
for (; unsigned(l) != shared; l &= l - 1) result -= data[l - 1];
for (; unsigned(r) != shared; r &= r - 1) result += data[r - 1];
return result;
}
T prod(int l, int r) const { return prod_as<T>(l, r); }
};
#line 1 "components/radix_sort.hpp"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <type_traits>
#include <utility>
#line 11 "components/radix_sort.hpp"
template <int Bits, class T, class Key>
void stable_radix_sort(std::vector<T> &values, Key &&key) {
static_assert(1 <= Bits && Bits <= 16);
constexpr unsigned buckets = 1U << Bits, mask = buckets - 1;
using K = std::remove_cvref_t<decltype(key(std::declval<const T &>()))>;
static_assert(std::is_integral_v<K> && std::is_unsigned_v<K> && sizeof(K) <= sizeof(std::uint64_t));
if (values.size() < 2) return;
if (values.size() < 256) {
std::stable_sort(values.begin(), values.end(), [&](const T &a, const T &b) { return key(a) < key(b); });
return;
}
std::uint64_t first = key(values.front()), previous = first, varying = 0;
bool sorted = true;
for (std::size_t i = 1; i < values.size(); ++i) {
std::uint64_t current = key(values[i]);
varying |= first ^ current;
sorted &= previous <= current;
previous = current;
}
if (sorted) return;
std::vector<T> scratch(values.size());
using Counts = std::conditional_t<(Bits <= 11), std::array<std::size_t, buckets>, std::vector<std::size_t>>;
Counts count = [] {
if constexpr (Bits <= 11) return Counts{};
else return Counts(buckets);
}();
for (int shift = 0; shift < 64; shift += Bits) {
if (!((varying >> shift) & mask)) continue;
std::fill(count.begin(), count.end(), 0);
for (const auto &value : values) ++count[(static_cast<std::uint64_t>(key(value)) >> shift) & mask];
std::size_t sum = 0;
for (auto &x : count) {
auto n = x;
x = sum, sum += n;
}
for (auto &&value : values) scratch[count[(static_cast<std::uint64_t>(key(value)) >> shift) & mask]++] = std::move(value);
values.swap(scratch);
}
}
template <class T, class Key>
void stable_radix_sort(std::vector<T> &values, Key key) {
stable_radix_sort<8>(values, std::move(key));
}
namespace radix_sort_detail {
template <int Bits = 8, class T, class Key>
void sort_by_key(std::vector<T> &values, Key key) {
using K = std::remove_cvref_t<decltype(key(std::declval<const T &>()))>;
if constexpr (std::is_integral_v<K> && !std::is_same_v<K, bool> && sizeof(K) <= 8 && std::is_default_constructible_v<T>) {
using U = std::make_unsigned_t<K>;
constexpr U sign = std::is_signed_v<K> ? U(1) << (sizeof(K) * 8 - 1) : U(0);
stable_radix_sort<Bits>(values, [&](const T &value) { return U(U(key(value)) ^ sign); });
} else std::sort(values.begin(), values.end(), [&](const T &a, const T &b) { return key(a) < key(b); });
}
}
#line 12 "data_structure/offline_rectangle_sum.hpp"
template <class X = std::int64_t, class Y = X, class Sum = std::int64_t>
class OfflineRectangleSum {
using Accum = std::conditional_t<std::is_integral_v<Sum> && sizeof(Sum) <= 8, __int128_t, Sum>;
struct Point { X x; Y y; Sum weight; int rank = 0; };
struct Query { X left, right; Y down, up; };
struct Event { X x; int down, up, query; bool subtract; };
std::vector<Point> points;
std::vector<Query> queries;
template <class A>
static void sweep(const std::vector<Point> &ordered, const std::vector<Event> &events, int size, std::vector<Accum> &answers) {
FenwickSum<A> tree(size);
std::size_t p = 0;
for (const auto &event : events) {
while (p < ordered.size() && ordered[p].x < event.x) {
tree.add(ordered[p].rank, A(ordered[p].weight));
++p;
}
auto value = tree.template prod_as<Accum>(event.down, event.up);
if (event.subtract) answers[event.query] -= value;
else answers[event.query] += value;
}
}
public:
explicit OfflineRectangleSum(int point_capacity = 0, int query_capacity = 0) {
assert(point_capacity >= 0 && query_capacity >= 0);
points.reserve(point_capacity); queries.reserve(query_capacity);
}
void add_point(const X &x, const Y &y, const Sum &weight) {
assert(points.size() < std::size_t(std::numeric_limits<int>::max()));
points.push_back({x, y, weight});
}
int add_query(const X &left, const Y &down, const X &right, const Y &up) {
assert(queries.size() < std::size_t(std::numeric_limits<int>::max()));
queries.push_back({left, right, down, up});
return int(queries.size() - 1);
}
std::vector<Sum> solve() const {
if (points.empty() || queries.empty()) return std::vector<Sum>(queries.size());
std::vector<Accum> answers(queries.size());
auto ordered = points;
radix_sort_detail::sort_by_key(ordered, [](const Point &p) -> const Y & { return p.y; });
std::vector<Y> ys;
ys.reserve(points.size());
for (auto &p : ordered) {
if (ys.empty() || ys.back() < p.y) ys.push_back(p.y);
p.rank = int(ys.size() - 1);
}
radix_sort_detail::sort_by_key(ordered, [](const Point &p) -> const X & { return p.x; });
std::vector<Event> events;
events.reserve(2 * queries.size());
for (int i = 0; i < int(queries.size()); ++i) {
const auto &q = queries[i];
if (!(q.left < q.right) || !(q.down < q.up)) continue;
int down = std::lower_bound(ys.begin(), ys.end(), q.down) - ys.begin();
int up = std::lower_bound(ys.begin(), ys.end(), q.up) - ys.begin();
if (down == up) continue;
events.push_back({q.left, down, up, i, true});
events.push_back({q.right, down, up, i, false});
}
radix_sort_detail::sort_by_key(events, [](const Event &e) -> const X & { return e.x; });
bool narrow = false;
if constexpr (std::is_integral_v<Sum> && sizeof(Sum) <= 8) {
Accum positive = 0, negative = 0;
for (const auto &point : points) {
Accum value(point.weight);
if (value > 0) positive += value;
else negative += value;
}
narrow = positive <= INT64_MAX && negative >= INT64_MIN;
}
if (narrow) {
if constexpr (std::is_integral_v<Sum> && sizeof(Sum) <= 8) sweep<std::int64_t>(ordered, events, int(ys.size()), answers);
} else sweep<Accum>(ordered, events, int(ys.size()), answers);
if constexpr (std::is_same_v<Accum, Sum>) return answers;
else return std::vector<Sum>(answers.begin(), answers.end());
}
};