競技プログラミング用 C++ ライブラリ
data_structure/offline_range_sum.hpp · GitHub で見る
共通ページ
#include "lib/data_structure/offline_range_sum.hpp"
OfflineRangeSum<int> query(std::vector<int>{4, 1, 4, 2});
query.add_query(0, 4, 4);
query.add_query(1, 3, 5);
auto answer = query.solve(); // { {3, 2}, {5, 2} } : {和, 個数}
| 操作 | 内容・返り値 |
|---|---|
OfflineRangeSum<T,Sum>(values) / build(values)
|
初期列を設定し、登録済みの問い合わせを消去 |
size() |
列の長さ |
reserve(q) |
問い合わせ数を予約 |
add_query(l,r,upper) |
[l,r) にある value < upper の要素の問い合わせを追加し、答えの添字を返す |
solve() |
{和,個数} を登録順の vector<pair<Sum,int>> で返す |
添字は 0 始まり、0 <= l <= r <= size() とします。値が upper と等しい要素は含めません。空区間や条件に合う要素がない場合の答えは {0,0} です。solve() は登録内容を変更せず、繰り返し呼び出せます。入力配列は変更せず、構築後に破棄できます。
T はコピー・大小比較が可能で、Sum に変換できる型とします。Sum は 0 による初期化と加減算に対応し、集計結果が収まる型を指定します。既定の Sum は、64 bit 未満の整数 T では int64_t、64 bit 整数では __int128_t、それ以外では T です。列長・問い合わせ数は INT_MAX 以下とします。
列長を \(N\)、問い合わせ数を \(Q\) とし、要素の演算を定数時間として、構築は \(O(N)\)、登録は償却 \(O(1)\)、solve() は \(O((N+Q)\log(N+Q))\) 時間、空間は \(O(N+Q)\) です。
#ifndef OFFLINE_RANGE_SUM_HPP
#define OFFLINE_RANGE_SUM_HPP
#include "../components/insert_only_prefix_count_sum.hpp"
#include "../components/radix_sort.hpp"
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <limits>
#include <type_traits>
#include <utility>
#include <vector>
template <class T, class Sum = std::conditional_t<std::is_integral_v<T>, std::conditional_t<(sizeof(T) < 8), std::int64_t, __int128_t>, T>>
class OfflineRangeSum {
struct Query { T upper; int first, last, index; };
struct Item { T value; int position; };
std::vector<T> values;
std::vector<Query> queries;
public:
using sum_type = Sum;
OfflineRangeSum() = default;
explicit OfflineRangeSum(const std::vector<T> &a) { build(a); }
void build(const std::vector<T> &a) {
assert(a.size() <= std::size_t(std::numeric_limits<int>::max()));
values = a; queries.clear();
}
int size() const { return int(values.size()); }
void reserve(int count) { assert(count >= 0); queries.reserve(count); }
int add_query(int l, int r, const T &upper) {
assert(0 <= l && l <= r && r <= size() && queries.size() < std::size_t(std::numeric_limits<int>::max()));
queries.push_back({upper, l, r, int(queries.size())});
return int(queries.size()) - 1;
}
std::vector<std::pair<Sum, int>> solve() const {
std::vector<std::pair<Sum, int>> result(queries.size());
if (queries.empty() || values.empty()) return result;
std::vector<Item> order;
order.reserve(values.size());
for (int i = 0; i < size(); ++i) order.push_back({values[i], i});
radix_sort_detail::sort_by_key<11>(order, [](const Item &item) { return item.value; });
auto sorted = queries;
radix_sort_detail::sort_by_key<11>(sorted, [](const Query &query) { return query.upper; });
if constexpr (std::is_integral_v<T> && sizeof(T) <= 8 && (std::is_integral_v<Sum> || std::is_same_v<Sum, __int128_t> || std::is_same_v<Sum, __uint128_t>)) {
int block_length = std::min(size(), insert_only_prefix_detail::block_size);
__int128_t base = order.front().value;
__uint128_t bound = (__int128_t(order.back().value) - base) * __uint128_t(block_length);
int bits = 1;
while (bound >>= 1) ++bits;
if (bits + std::bit_width(unsigned(block_length)) <= 64) {
insert_only_prefix_detail::Packed tree(size(), base, bits);
std::size_t inserted = 0;
for (const auto &query : sorted) {
while (inserted < order.size() && order[inserted].value < query.upper) {
const auto &item = order[inserted++];
tree.insert(item.position, std::uint64_t(__int128_t(item.value) - base));
}
auto answer = tree.fold(query.first, query.last);
result[query.index] = {Sum(answer.sum), answer.count};
}
return result;
}
}
using Accum = std::conditional_t<std::is_integral_v<Sum> && sizeof(Sum) <= 8, __int128_t, Sum>;
struct Payload {
Accum sum{}; int count = 0;
Payload &operator+=(const Payload &other) { sum += other.sum; count += other.count; return *this; }
Payload &operator-=(const Payload &other) { sum -= other.sum; count -= other.count; return *this; }
};
FenwickSum<Payload> tree(size());
std::size_t inserted = 0;
for (const auto &query : sorted) {
while (inserted < order.size() && order[inserted].value < query.upper) {
const auto &item = order[inserted++];
tree.add(item.position, {Accum(item.value), 1});
}
auto answer = tree.prod(query.first, query.last);
result[query.index] = {Sum(answer.sum), answer.count};
}
return result;
}
};
#endif
#line 1 "data_structure/offline_range_sum.hpp"
#line 1 "components/insert_only_prefix_count_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 5 "components/insert_only_prefix_count_sum.hpp"
#include <algorithm>
#include <array>
#line 9 "components/insert_only_prefix_count_sum.hpp"
#include <cstdint>
#line 11 "components/insert_only_prefix_count_sum.hpp"
#if defined(__x86_64__) || defined(__i386__)
#include <immintrin.h>
#endif
namespace insert_only_prefix_detail {
inline constexpr int level_count = 4, block_bits = 4 * level_count, block_size = 1 << block_bits;
struct CountSum {
__int128_t sum = 0;
int count = 0;
CountSum &operator+=(const CountSum &other) { sum += other.sum; count += other.count; return *this; }
CountSum &operator-=(const CountSum &other) { sum -= other.sum; count -= other.count; return *this; }
};
class Packed {
std::array<std::vector<std::array<std::uint64_t, 16>>, level_count> levels;
FenwickSum<CountSum> coarse;
__int128_t base;
std::uint64_t sum_mask;
int sum_bits;
[[maybe_unused]] bool vectorized = false;
static void add_scalar(std::uint64_t *values, int first, std::uint64_t delta) {
for (int i = first; i < 16; ++i) values[i] += delta;
}
#if defined(__x86_64__) || defined(__i386__)
__attribute__((target("avx2"), always_inline)) static void add_avx2(std::uint64_t *values, int first, std::uint64_t delta) {
if (first == 16) return;
int i = first & ~3;
auto change = _mm256_set1_epi64x(static_cast<long long>(delta));
auto active = _mm256_cmpgt_epi64(_mm256_setr_epi64x(i, i + 1, i + 2, i + 3), _mm256_set1_epi64x(first - 1));
auto p = reinterpret_cast<__m256i *>(values + i);
_mm256_storeu_si256(p, _mm256_add_epi64(_mm256_loadu_si256(p), _mm256_and_si256(change, active)));
for (i += 4; i < 16; i += 4) {
p = reinterpret_cast<__m256i *>(values + i);
_mm256_storeu_si256(p, _mm256_add_epi64(_mm256_loadu_si256(p), change));
}
}
#endif
#if defined(__x86_64__) || defined(__i386__)
__attribute__((target("avx2"))) void insert_avx2(unsigned p, std::uint64_t delta) {
auto encoded = (std::uint64_t(1) << sum_bits) + delta;
add_avx2(levels[0][p >> 4].data(), p & 15, encoded);
for (int level = 1; level < level_count; ++level)
add_avx2(levels[level][p >> (4 * (level + 1))].data(), ((p >> (4 * level)) & 15) + 1, encoded);
coarse.add(p >> block_bits, {base + delta, 1});
}
#endif
public:
Packed(int n, __int128_t minimum, int bits) : coarse((std::size_t(n) + block_size - 1) >> block_bits), base(minimum), sum_mask((std::uint64_t(1) << bits) - 1), sum_bits(bits) {
assert(n >= 0 && 0 < bits && bits < 64);
for (int level = 0; level < level_count; ++level) levels[level].resize((std::size_t(n) + (std::size_t(1) << (4 * (level + 1))) - 1) >> (4 * (level + 1)));
#if defined(__x86_64__) || defined(__i386__)
vectorized = __builtin_cpu_supports("avx2");
#endif
}
void insert(int p, std::uint64_t delta) {
#if defined(__x86_64__) || defined(__i386__)
if (vectorized) { insert_avx2(unsigned(p), delta); return; }
#endif
auto encoded = (std::uint64_t(1) << sum_bits) + delta;
add_scalar(levels[0][unsigned(p) >> 4].data(), p & 15, encoded);
for (int level = 1; level < level_count; ++level)
add_scalar(levels[level][unsigned(p) >> (4 * (level + 1))].data(), ((unsigned(p) >> (4 * level)) & 15) + 1, encoded);
coarse.add(unsigned(p) >> block_bits, {base + delta, 1});
}
CountSum prefix(int r) const {
if (!r) return {};
unsigned p = unsigned(r - 1);
std::uint64_t encoded = levels[0][p >> 4][p & 15];
for (int level = 1; level < level_count; ++level) encoded += levels[level][p >> (4 * (level + 1))][(p >> (4 * level)) & 15];
auto result = coarse.prefix(p >> block_bits);
int count = int(encoded >> sum_bits);
result.sum += __int128_t(encoded & sum_mask) + base * count;
result.count += count;
return result;
}
CountSum fold(int l, int r) const {
auto result = prefix(r);
return result -= prefix(l);
}
};
}
#line 1 "components/radix_sort.hpp"
#line 6 "components/radix_sort.hpp"
#include <cstddef>
#line 8 "components/radix_sort.hpp"
#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 13 "data_structure/offline_range_sum.hpp"
template <class T, class Sum = std::conditional_t<std::is_integral_v<T>, std::conditional_t<(sizeof(T) < 8), std::int64_t, __int128_t>, T>>
class OfflineRangeSum {
struct Query { T upper; int first, last, index; };
struct Item { T value; int position; };
std::vector<T> values;
std::vector<Query> queries;
public:
using sum_type = Sum;
OfflineRangeSum() = default;
explicit OfflineRangeSum(const std::vector<T> &a) { build(a); }
void build(const std::vector<T> &a) {
assert(a.size() <= std::size_t(std::numeric_limits<int>::max()));
values = a; queries.clear();
}
int size() const { return int(values.size()); }
void reserve(int count) { assert(count >= 0); queries.reserve(count); }
int add_query(int l, int r, const T &upper) {
assert(0 <= l && l <= r && r <= size() && queries.size() < std::size_t(std::numeric_limits<int>::max()));
queries.push_back({upper, l, r, int(queries.size())});
return int(queries.size()) - 1;
}
std::vector<std::pair<Sum, int>> solve() const {
std::vector<std::pair<Sum, int>> result(queries.size());
if (queries.empty() || values.empty()) return result;
std::vector<Item> order;
order.reserve(values.size());
for (int i = 0; i < size(); ++i) order.push_back({values[i], i});
radix_sort_detail::sort_by_key<11>(order, [](const Item &item) { return item.value; });
auto sorted = queries;
radix_sort_detail::sort_by_key<11>(sorted, [](const Query &query) { return query.upper; });
if constexpr (std::is_integral_v<T> && sizeof(T) <= 8 && (std::is_integral_v<Sum> || std::is_same_v<Sum, __int128_t> || std::is_same_v<Sum, __uint128_t>)) {
int block_length = std::min(size(), insert_only_prefix_detail::block_size);
__int128_t base = order.front().value;
__uint128_t bound = (__int128_t(order.back().value) - base) * __uint128_t(block_length);
int bits = 1;
while (bound >>= 1) ++bits;
if (bits + std::bit_width(unsigned(block_length)) <= 64) {
insert_only_prefix_detail::Packed tree(size(), base, bits);
std::size_t inserted = 0;
for (const auto &query : sorted) {
while (inserted < order.size() && order[inserted].value < query.upper) {
const auto &item = order[inserted++];
tree.insert(item.position, std::uint64_t(__int128_t(item.value) - base));
}
auto answer = tree.fold(query.first, query.last);
result[query.index] = {Sum(answer.sum), answer.count};
}
return result;
}
}
using Accum = std::conditional_t<std::is_integral_v<Sum> && sizeof(Sum) <= 8, __int128_t, Sum>;
struct Payload {
Accum sum{}; int count = 0;
Payload &operator+=(const Payload &other) { sum += other.sum; count += other.count; return *this; }
Payload &operator-=(const Payload &other) { sum -= other.sum; count -= other.count; return *this; }
};
FenwickSum<Payload> tree(size());
std::size_t inserted = 0;
for (const auto &query : sorted) {
while (inserted < order.size() && order[inserted].value < query.upper) {
const auto &item = order[inserted++];
tree.add(item.position, {Accum(item.value), 1});
}
auto answer = tree.prod(query.first, query.last);
result[query.index] = {Sum(answer.sum), answer.count};
}
return result;
}
};