競技プログラミング用 C++ ライブラリ
data_structure/kinetic_tournament.hpp · GitHub で見る
共通ページ
固定した昇順の係数列 \(V\) と値の列 \(W\) を管理します。区間内で \(W_i\gets W_i+aV_i+b\) とする更新と、区間の最大値・その添字を求めます。\(a\geq0\) が必要です。\(b\) は負でも構いません。
#include "lib/data_structure/kinetic_tournament.hpp"
int main() {
std::vector<std::int64_t> coefficients{1, 3, 5};
std::vector<std::int64_t> initial{10, 4, 0};
KineticTournament<> tree(coefficients, initial);
assert(tree.all_prod()->index == 0);
tree.advance(0, 3, 2);
assert(tree.all_prod()->value == 12);
tree.apply(1, 3, 1, -2);
auto best = tree.prod(0, 3);
assert(best && best->index == 2 && best->value == 13);
tree.add(0, 1, 5);
assert(tree.all_prod()->index == 0 && tree.get(0) == 17);
tree.erase(0);
assert(!tree.get(0) && tree.active_size() == 2);
tree.set(0, 100);
assert(tree.get(0) == 100);
}
V には 重複を許す昇順 の列を渡します。内部では並べ替えません。添字の並びと係数は構築後に固定されます。set(i,value) が変更するのは値 \(W_i\) で、係数 \(V_i\) は変更しません。係数の変更には build による再構築が必要です。
区間は全て 0 始まりの半開区間 [l,r) です。apply(l,r,a,b) は、その時点で有効な区間内の各要素に a*V[i]+b を加えます。add(l,r,b) は一様加算、advance(l,r,t) は t*V[i] の加算です。t は絶対時刻ではなく、今回加える 非負の増分 です。区間ごとに別々の量を加えられます。空区間への更新は何もしません。
KineticTournament<> は最大値版、KineticTournament<T,false> は最小値版です。返り値は optional<Result> で、Result.index が添字、Result.value が値です。同値の場合、最大値版は添字が大きい方、最小値版は添字が小さい方を返します。全体であれば all_prod()、区間であれば prod(l,r) を呼びます。空区間や有効な要素がない区間では nullopt です。
KineticTournament(V) は全要素の値を 0 にします。第 2 引数に値を 1 つ渡すと全要素をその値にし、vector<T> を渡すと各要素を設定します。KineticTournament(V,std::nullopt) は全位置を無効にして作ります。個別に無効な位置を指定したい場合は vector<optional<T>> を渡し、無効な位置に nullopt を入れます。
erase(i) は位置 i を無効にします。その位置は区間更新・クエリから除外され、set(i,value) で再び有効にできます。無効だった間の加算は、後から有効にした要素には反映されません。 番兵の値で無効な要素を表さないため、型の最小値・最大値も有効な値として使えます。
T は符号付き 64 bit 以下の整数型で、既定は int64_t です。係数・更新の引数・各更新後の有効な要素の値は T に収めてください。差、積、遅延更新の合成は __int128_t で計算します。内部で累積する更新係数・加算量と、その評価途中も符号付き 128 bit に収めてください。
| 操作 | 内容・返り値 | 計算量 |
|---|---|---|
KineticTournament() |
長さ 0 の構造 | \(O(1)\) |
KineticTournament(V,value=0) |
係数列と共通の初期値で構築 | \(O(n)\) |
KineticTournament(V,values) |
vector<T> / vector<optional<T>> の初期値で構築 |
\(O(n)\) |
KineticTournament(V,std::nullopt) |
全位置を無効にして構築 | \(O(n)\) |
build(V,value=0) / build(V,values) / build(V,std::nullopt)
|
上記と同じ指定で再構築 | 新旧の長さの合計に対して \(O(n)\) |
apply(l,r,a,b=0) |
区間で W[i] += a*V[i]+b。a >= 0
|
償却 \(O(\log^2 n)\)、1 回の最悪 \(O(n)\) |
add(l,r,b) |
区間で W[i] += b
|
\(O(\log n)\) |
advance(l,r,t=1) |
区間で W[i] += t*V[i]。t >= 0
|
償却 \(O(\log^2 n)\)、1 回の最悪 \(O(n)\) |
all_apply(a,b=0) |
全体で W[i] += a*V[i]+b
|
償却 \(O(\log^2 n)\)、1 回の最悪 \(O(n)\) |
prod(l,r) |
区間の最適な添字と値 | \(O(\log n)\) |
all_prod() |
全体の最適な添字と値 | \(O(1)\) |
set(i,value) |
値を設定して有効にする。係数は維持 | \(O(\log n)\) |
erase(i) |
無効にする。有効だった場合は true | \(O(\log n)\)、既に無効なら \(O(1)\) |
get(i) |
現在値を optional<T> で返す。無効なら nullopt
|
\(O(\log n)\)、無効なら \(O(1)\) |
values() |
全位置の現在値を vector<optional<T>> で返す |
\(O(n)\) |
slopes() |
固定係数列への const 参照 | \(O(1)\) |
size() / active_size() / empty()
|
添字数 / 有効な要素数 / 有効な要素がないか | \(O(1)\) |
clear() |
係数列と長さを保って全位置を無効にする | \(O(n)\) |
swap(other) |
内容を交換 | \(O(1)\) |
| コピー / ムーブ | 独立した複製 / 内容を移動。移動元は長さ 0 | コピー \(O(n)\)、ムーブ構築 \(O(1)\) |
\(n\) は添字数です。空間・構築は \(O(n)\)。初期構築後の \(q\) 操作の合計は \(O(n\log n+q\log^2 n)\) です。初期状態からの交差も数えるため、少数の更新だけで各更新が最悪 \(O(\log^2 n)\) になるという保証ではありません。係数順が固定され、重み付き加算の係数が非負であることを使った計算量です。
各節点に、現在の勝者と、部分木の勝者が変わらずに進められる増分を保存します。更新がその範囲を超えたときだけ子へ降り、勝者を計算し直します。実際に保持するのは交差前に安全な整数ステップ数で、上限値を「交差なし」に使います。汎用の SegtreeBeats を共有します。
#ifndef KINETIC_TOURNAMENT_HPP
#define KINETIC_TOURNAMENT_HPP
#include "segtree_beats.hpp"
#include "../components/kinetic_tournament_monoid.hpp"
#include <optional>
template<class T = std::int64_t, bool maximize = true> class KineticTournament {
using M = kinetic_tournament_detail::Monoid<T, maximize>;
using Node = typename M::Node;
SegtreeBeats<Node, M::op, M::identity, typename M::Tag, M::mapping, M::composition, M::tag_identity> tree_;
std::vector<T> slopes_;
std::vector<unsigned char> active_;
int count_ = 0;
void check([[maybe_unused]] int i) const { assert(0 <= i && i < size()); }
void check([[maybe_unused]] int l, [[maybe_unused]] int r) const { assert(0 <= l && l <= r && r <= size()); }
public:
struct Result { int index; T value; };
using value_type = T;
KineticTournament() = default;
explicit KineticTournament(const std::vector<T> &slopes, T value = 0) { build(slopes, value); }
KineticTournament(const std::vector<T> &slopes, const std::vector<T> &values) { build(slopes, values); }
KineticTournament(const std::vector<T> &slopes, const std::vector<std::optional<T>> &values) { build(slopes, values); }
KineticTournament(const std::vector<T> &slopes, std::nullopt_t) { build(slopes, std::nullopt); }
KineticTournament(const KineticTournament &) = default;
KineticTournament &operator=(const KineticTournament &) = default;
KineticTournament(KineticTournament &&other) : KineticTournament() { swap(other); }
KineticTournament &operator=(KineticTournament &&other) {
if (this != &other) { KineticTournament next(std::move(other)); swap(next); }
return *this;
}
void swap(KineticTournament &other) noexcept {
std::swap(tree_, other.tree_);
slopes_.swap(other.slopes_); active_.swap(other.active_); std::swap(count_, other.count_);
}
void build(const std::vector<T> &slopes, const std::vector<std::optional<T>> &values) {
assert(slopes.size() == values.size() && slopes.size() <= std::size_t(std::numeric_limits<int>::max() / 4));
assert(std::is_sorted(slopes.begin(), slopes.end()));
slopes_ = slopes;
active_.assign(slopes.size(), 0);
count_ = 0;
std::vector<Node> nodes(slopes.size());
for (int i = 0; i < size(); ++i) if (values[i]) {
nodes[i] = {*values[i], slopes_[i], M::never, i};
active_[i] = 1; ++count_;
}
tree_.build(nodes);
}
void build(const std::vector<T> &slopes, const std::vector<T> &values) { build(slopes, std::vector<std::optional<T>>(values.begin(), values.end())); }
void build(const std::vector<T> &slopes, T value = 0) { build(slopes, std::vector<std::optional<T>>(slopes.size(), value)); }
void build(const std::vector<T> &slopes, std::nullopt_t) { build(slopes, std::vector<std::optional<T>>(slopes.size())); }
int size() const { return static_cast<int>(slopes_.size()); }
int active_size() const { return count_; }
bool empty() const { return count_ == 0; }
const std::vector<T> &slopes() const { return slopes_; }
void clear() { tree_.build(std::vector<Node>(size())); std::fill(active_.begin(), active_.end(), 0); count_ = 0; }
void set(int i, T value) {
check(i);
tree_.set(i, Node{value, slopes_[i], M::never, i});
count_ += !active_[i]; active_[i] = 1;
}
bool erase(int i) {
check(i);
if (!active_[i]) return false;
tree_.set(i, M::identity());
active_[i] = 0; --count_;
return true;
}
std::optional<T> get(int i) { check(i); return active_[i] ? std::optional<T>(tree_.get(i).value) : std::nullopt; }
void apply(int l, int r, T a, T b = 0) {
check(l, r); assert(a >= 0);
if (l == r || (!a && !b)) return;
if (!l && r == size()) tree_.apply({a, b});
else tree_.apply(l, r, {a, b});
}
void add(int l, int r, T value) { apply(l, r, 0, value); }
void advance(int l, int r, T steps = 1) { apply(l, r, steps, 0); }
void all_apply(T a, T b = 0) { apply(0, size(), a, b); }
std::optional<Result> prod(int l, int r) {
check(l, r);
if (!l && r == size()) return all_prod();
auto node = tree_.prod(l, r);
return node.index == -1 ? std::nullopt : std::optional<Result>(Result{node.index, node.value});
}
std::optional<Result> all_prod() const {
auto node = tree_.all_prod();
return node.index == -1 ? std::nullopt : std::optional<Result>(Result{node.index, node.value});
}
std::vector<std::optional<T>> values() {
auto nodes = tree_.values();
std::vector<std::optional<T>> result(size());
for (int i = 0; i < size(); ++i) if (active_[i]) result[i] = nodes[i].value;
return result;
}
};
#endif
#line 1 "data_structure/kinetic_tournament.hpp"
#line 1 "data_structure/segtree_beats.hpp"
#include <algorithm>
#include <bit>
#include <cassert>
#include <cstddef>
#include <limits>
#include <utility>
#include <vector>
template <class S, auto op, auto e, class F, auto mapping, auto composition, auto id>
class SegtreeBeats {
int n_ = 0, base_ = 1, log_ = 0;
std::vector<S> data_;
std::vector<F> lazy_;
std::vector<unsigned char> dirty_;
void check([[maybe_unused]] int l, [[maybe_unused]] int r) const {
assert(0 <= l && l <= r && r <= n_);
}
void pull(int k) { data_[k] = op(data_[2 * k], data_[2 * k + 1]); }
void all_apply(int k, const F &f) {
if (k < base_) {
lazy_[k] = dirty_[k] ? composition(f, lazy_[k]) : f;
dirty_[k] = 1;
}
if (mapping(f, data_[k])) return;
assert(k < base_);
push(k);
pull(k);
}
void push(int k) {
if (!dirty_[k]) return;
all_apply(2 * k, lazy_[k]);
all_apply(2 * k + 1, lazy_[k]);
dirty_[k] = 0;
}
void push_path(int k) {
for (int h = log_; h; --h) push(k >> h);
}
void push_range(int l, int r) {
for (int h = log_; h; --h) {
if (((l >> h) << h) != l) push(l >> h);
if (((r >> h) << h) != r) push((r - 1) >> h);
}
}
void pull_range(int l, int r) {
for (int h = 1; h <= log_; ++h) {
if (((l >> h) << h) != l) pull(l >> h);
if (((r >> h) << h) != r) pull((r - 1) >> h);
}
}
public:
SegtreeBeats() : SegtreeBeats(0) {}
explicit SegtreeBeats(int n, const S &value = e()) {
assert(0 <= n && n <= std::numeric_limits<int>::max() / 4);
build(std::vector<S>(n, value));
}
explicit SegtreeBeats(const std::vector<S> &a) { build(a); }
void build(const std::vector<S> &a) {
assert(a.size() <= std::size_t(std::numeric_limits<int>::max() / 4));
n_ = static_cast<int>(a.size());
base_ = static_cast<int>(std::bit_ceil(static_cast<unsigned>(std::max(1, n_))));
log_ = std::countr_zero(static_cast<unsigned>(base_));
data_.assign(2 * base_, e());
lazy_.assign(base_, id());
dirty_.assign(base_, 0);
std::copy(a.begin(), a.end(), data_.begin() + base_);
for (int k = base_ - 1; k; --k) pull(k);
}
int size() const { return n_; }
bool empty() const { return !n_; }
S all_prod() const { return data_[1]; }
S get(int p) {
assert(0 <= p && p < n_);
int k = p + base_;
push_path(k);
return data_[k];
}
void set(int p, const S &value) {
assert(0 <= p && p < n_);
int k = p + base_;
push_path(k);
data_[k] = value;
while (k >>= 1) pull(k);
}
S prod(int l, int r) {
check(l, r);
if (l == r) return e();
l += base_, r += base_;
push_range(l, r);
S left = e(), right = e();
while (l < r) {
if (l & 1) left = op(std::move(left), data_[l++]);
if (r & 1) right = op(data_[--r], std::move(right));
l >>= 1, r >>= 1;
}
return op(std::move(left), std::move(right));
}
void apply(const F &f) { if (n_) all_apply(1, f); }
void apply(int p, const F &f) {
assert(0 <= p && p < n_);
int k = p + base_;
push_path(k);
all_apply(k, f);
while (k >>= 1) pull(k);
}
void apply(int l, int r, const F &f) {
check(l, r);
if (l == r) return;
l += base_, r += base_;
push_range(l, r);
int first = l, last = r;
while (l < r) {
if (l & 1) all_apply(l++, f);
if (r & 1) all_apply(--r, f);
l >>= 1, r >>= 1;
}
pull_range(first, last);
}
template <class Predicate>
int max_right(int l, Predicate predicate) {
assert(0 <= l && l <= n_ && predicate(e()));
if (l == n_) return n_;
int k = l + base_;
push_path(k);
S value = e();
do {
while (!(k & 1)) k >>= 1;
S next = op(value, data_[k]);
if (!predicate(next)) {
while (k < base_) {
push(k);
k *= 2;
next = op(value, data_[k]);
if (predicate(next)) value = std::move(next), ++k;
}
return std::min(k - base_, n_);
}
value = std::move(next);
++k;
} while ((k & -k) != k);
return n_;
}
template <class Predicate>
int min_left(int r, Predicate predicate) {
assert(0 <= r && r <= n_ && predicate(e()));
if (!r) return 0;
int k = r + base_;
push_path(k - 1);
S value = e();
do {
--k;
while (k > 1 && (k & 1)) k >>= 1;
S next = op(data_[k], value);
if (!predicate(next)) {
while (k < base_) {
push(k);
k = 2 * k + 1;
next = op(data_[k], value);
if (predicate(next)) value = std::move(next), --k;
}
return k + 1 - base_;
}
value = std::move(next);
} while ((k & -k) != k);
return 0;
}
std::vector<S> values() {
for (int k = 1; k < base_; ++k) push(k);
return std::vector<S>(data_.begin() + base_, data_.begin() + base_ + n_);
}
};
#line 1 "components/kinetic_tournament_monoid.hpp"
#line 6 "components/kinetic_tournament_monoid.hpp"
#include <cstdint>
#line 8 "components/kinetic_tournament_monoid.hpp"
#include <type_traits>
namespace kinetic_tournament_detail {
template<class T, bool maximize> struct Monoid {
static_assert(std::is_integral_v<T> && std::is_signed_v<T> && sizeof(T) <= 8);
using W = __int128_t;
using U = std::uint64_t;
static constexpr U never = std::numeric_limits<U>::max();
struct Node { T value = 0, slope = 0; U remaining = never; int index = -1; };
struct Tag { W steps = 0, add = 0; };
static Node identity() { return {}; }
static Tag tag_identity() { return {}; }
static bool better(const Node &a, const Node &b) {
if (a.index == -1) return false;
if (b.index == -1) return true;
if (a.value != b.value) return maximize ? a.value > b.value : a.value < b.value;
return maximize ? a.index > b.index : a.index < b.index;
}
static Node op(Node a, Node b) {
if (a.index == -1) return b;
if (b.index == -1) return a;
bool left = better(a, b);
Node result = left ? a : b;
result.remaining = std::min(a.remaining, b.remaining);
if (left == maximize && a.slope < b.slope) {
U gap = U(a.value) - U(b.value);
U difference = U(b.slope) - U(a.slope);
assert(gap > 0);
result.remaining = std::min(result.remaining, (gap - 1) / difference);
}
return result;
}
static bool mapping(Tag tag, Node &node) {
if (node.index == -1) return true;
if (node.remaining != never && tag.steps > W(node.remaining)) return false;
W value = W(node.value) + tag.steps * node.slope + tag.add;
assert(std::numeric_limits<T>::lowest() <= value && value <= std::numeric_limits<T>::max());
node.value = T(value);
if (node.remaining != never) node.remaining -= U(tag.steps);
return true;
}
static Tag composition(Tag a, Tag b) { return {a.steps + b.steps, a.add + b.add}; }
};
}
#line 6 "data_structure/kinetic_tournament.hpp"
#include <optional>
template<class T = std::int64_t, bool maximize = true> class KineticTournament {
using M = kinetic_tournament_detail::Monoid<T, maximize>;
using Node = typename M::Node;
SegtreeBeats<Node, M::op, M::identity, typename M::Tag, M::mapping, M::composition, M::tag_identity> tree_;
std::vector<T> slopes_;
std::vector<unsigned char> active_;
int count_ = 0;
void check([[maybe_unused]] int i) const { assert(0 <= i && i < size()); }
void check([[maybe_unused]] int l, [[maybe_unused]] int r) const { assert(0 <= l && l <= r && r <= size()); }
public:
struct Result { int index; T value; };
using value_type = T;
KineticTournament() = default;
explicit KineticTournament(const std::vector<T> &slopes, T value = 0) { build(slopes, value); }
KineticTournament(const std::vector<T> &slopes, const std::vector<T> &values) { build(slopes, values); }
KineticTournament(const std::vector<T> &slopes, const std::vector<std::optional<T>> &values) { build(slopes, values); }
KineticTournament(const std::vector<T> &slopes, std::nullopt_t) { build(slopes, std::nullopt); }
KineticTournament(const KineticTournament &) = default;
KineticTournament &operator=(const KineticTournament &) = default;
KineticTournament(KineticTournament &&other) : KineticTournament() { swap(other); }
KineticTournament &operator=(KineticTournament &&other) {
if (this != &other) { KineticTournament next(std::move(other)); swap(next); }
return *this;
}
void swap(KineticTournament &other) noexcept {
std::swap(tree_, other.tree_);
slopes_.swap(other.slopes_); active_.swap(other.active_); std::swap(count_, other.count_);
}
void build(const std::vector<T> &slopes, const std::vector<std::optional<T>> &values) {
assert(slopes.size() == values.size() && slopes.size() <= std::size_t(std::numeric_limits<int>::max() / 4));
assert(std::is_sorted(slopes.begin(), slopes.end()));
slopes_ = slopes;
active_.assign(slopes.size(), 0);
count_ = 0;
std::vector<Node> nodes(slopes.size());
for (int i = 0; i < size(); ++i) if (values[i]) {
nodes[i] = {*values[i], slopes_[i], M::never, i};
active_[i] = 1; ++count_;
}
tree_.build(nodes);
}
void build(const std::vector<T> &slopes, const std::vector<T> &values) { build(slopes, std::vector<std::optional<T>>(values.begin(), values.end())); }
void build(const std::vector<T> &slopes, T value = 0) { build(slopes, std::vector<std::optional<T>>(slopes.size(), value)); }
void build(const std::vector<T> &slopes, std::nullopt_t) { build(slopes, std::vector<std::optional<T>>(slopes.size())); }
int size() const { return static_cast<int>(slopes_.size()); }
int active_size() const { return count_; }
bool empty() const { return count_ == 0; }
const std::vector<T> &slopes() const { return slopes_; }
void clear() { tree_.build(std::vector<Node>(size())); std::fill(active_.begin(), active_.end(), 0); count_ = 0; }
void set(int i, T value) {
check(i);
tree_.set(i, Node{value, slopes_[i], M::never, i});
count_ += !active_[i]; active_[i] = 1;
}
bool erase(int i) {
check(i);
if (!active_[i]) return false;
tree_.set(i, M::identity());
active_[i] = 0; --count_;
return true;
}
std::optional<T> get(int i) { check(i); return active_[i] ? std::optional<T>(tree_.get(i).value) : std::nullopt; }
void apply(int l, int r, T a, T b = 0) {
check(l, r); assert(a >= 0);
if (l == r || (!a && !b)) return;
if (!l && r == size()) tree_.apply({a, b});
else tree_.apply(l, r, {a, b});
}
void add(int l, int r, T value) { apply(l, r, 0, value); }
void advance(int l, int r, T steps = 1) { apply(l, r, steps, 0); }
void all_apply(T a, T b = 0) { apply(0, size(), a, b); }
std::optional<Result> prod(int l, int r) {
check(l, r);
if (!l && r == size()) return all_prod();
auto node = tree_.prod(l, r);
return node.index == -1 ? std::nullopt : std::optional<Result>(Result{node.index, node.value});
}
std::optional<Result> all_prod() const {
auto node = tree_.all_prod();
return node.index == -1 ? std::nullopt : std::optional<Result>(Result{node.index, node.value});
}
std::vector<std::optional<T>> values() {
auto nodes = tree_.values();
std::vector<std::optional<T>> result(size());
for (int i = 0; i < size(); ++i) if (active_[i]) result[i] = nodes[i].value;
return result;
}
};