競技プログラミング用 C++ ライブラリ
data_structure/slope_trick.hpp · GitHub で見る
共通ページ
#include "lib/data_structure/slope_trick.hpp"
SlopeTrick<> f;
f.add_abs(3);
f.add_x_minus_a(1);
f.shift(-1, 2);
auto value = f.min();
auto [left, right] = f.argmin();
auto y = f.eval(5);
初期状態は \(f(x)=0\) です。座標型・値の型は SlopeTrick<T, Sum> で指定し、既定はともに std::int64_t です。座標と遅延値の計算は T、関数値の計算は Sum に収めてください。
| 操作 | 内容・返り値 | 計算量 |
|---|---|---|
SlopeTrick() |
\(f(x)=0\) で初期化 | \(O(1)\) |
add_all(c) |
\(f(x)+c\) | \(O(1)\) |
add_a_minus_x(a) |
\(f(x)+\max(a-x,0)\) | 償却 \(O(\log n)\) |
add_x_minus_a(a) |
\(f(x)+\max(x-a,0)\) | 償却 \(O(\log n)\) |
add_abs(a) |
\(f(x)+\lvert x-a\rvert\) | 償却 \(O(\log n)\) |
shift(a) |
\(f(x-a)\) | \(O(1)\) |
shift(a,b) |
\(\displaystyle \min_{x-b\le y\le x-a}f(y)\)、\(a\le b\) | \(O(1)\) |
clear_right() |
\(\displaystyle \min_{y\le x}f(y)\) | \(O(1)\) |
clear_left() |
\(\displaystyle \min_{y\ge x}f(y)\) | \(O(1)\) |
min() |
最小値 | \(O(1)\) |
argmin() |
最小値を取る閉区間の両端。無限側は nullopt
|
\(O(1)\) |
eval(x) |
\(f(x)\)。関数は変更しない | \(O(n)\) |
merge(g) / merge(std::move(g))
|
\(f(x)+g(x)\)。g は初期状態に戻る |
\(O(\min(n,m)\log(n+m))\)、左辺値のコピーは別途 \(O(m)\) |
size() / empty()
|
傾きの変化点数/変化点がないか | \(O(1)\) |
reserve(n) / clear()
|
左右にそれぞれ n 点分を予約/容量を残して初期状態に戻す |
reserve: \(O(n)\)、clear: \(O(1)\) |
swap(g) |
関数と確保領域を交換 | \(O(1)\) |
変化点数を \(n\) として、折れ線の追加は償却 \(O(\log n)\)、eval は \(O(n)\)、その他の操作は \(O(1)\) です。変化点数 \(m\) の関数との merge は \(O(\min(n,m)\log(n+m))\)、空間は \(O(n)\) です。コピー・予約は \(O(n)\)、演算と要素のコピーは \(O(1)\) とします。
#ifndef SLOPE_TRICK_HPP
#define SLOPE_TRICK_HPP
#include "../components/d_ary_heap.hpp"
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <optional>
#include <type_traits>
#include <utility>
template <class T = std::int64_t, class Sum = T>
class SlopeTrick {
static_assert((std::is_arithmetic_v<T> && std::is_signed_v<T>) || std::is_same_v<T, __int128_t>);
static_assert((std::is_arithmetic_v<Sum> && std::is_signed_v<Sum>) || std::is_same_v<Sum, __int128_t>);
DaryHeap<T> left;
DaryHeap<T, std::greater<T>> right;
T offset_left = 0, offset_right = 0;
Sum minimum = 0;
public:
SlopeTrick() = default;
SlopeTrick(const SlopeTrick &) = default;
SlopeTrick &operator=(const SlopeTrick &) = default;
SlopeTrick(SlopeTrick &&other) noexcept : left(std::move(other.left)), right(std::move(other.right)), offset_left(std::exchange(other.offset_left, 0)), offset_right(std::exchange(other.offset_right, 0)), minimum(std::exchange(other.minimum, Sum(0))) {}
SlopeTrick &operator=(SlopeTrick &&other) noexcept {
if (this != &other) {
left = std::move(other.left);
right = std::move(other.right);
offset_left = std::exchange(other.offset_left, 0);
offset_right = std::exchange(other.offset_right, 0);
minimum = std::exchange(other.minimum, Sum(0));
}
return *this;
}
std::size_t size() const { return left.size() + right.size(); }
bool empty() const { return size() == 0; }
void reserve(std::size_t n) {
left.reserve(n);
right.reserve(n);
}
void clear_left() {
left.clear();
offset_left = 0;
}
void clear_right() {
right.clear();
offset_right = 0;
}
void clear() {
clear_left();
clear_right();
minimum = 0;
}
Sum min() const { return minimum; }
std::pair<std::optional<T>, std::optional<T>> argmin() const {
return {left.empty() ? std::nullopt : std::optional<T>(left.top() + offset_left), right.empty() ? std::nullopt : std::optional<T>(right.top() + offset_right)};
}
void add_all(Sum a) { minimum += a; }
void add_a_minus_x(T a) {
if (!right.empty() && right.top() + offset_right < a) {
T r = right.replace_top(a - offset_right) + offset_right;
minimum += Sum(a) - Sum(r);
left.push(r - offset_left);
} else left.push(a - offset_left);
}
void add_x_minus_a(T a) {
if (!left.empty() && a < left.top() + offset_left) {
T l = left.replace_top(a - offset_left) + offset_left;
minimum += Sum(l) - Sum(a);
right.push(l - offset_right);
} else right.push(a - offset_right);
}
void add_abs(T a) {
if (!left.empty() && a < left.top() + offset_left) {
T l = left.replace_top(a - offset_left) + offset_left;
minimum += Sum(l) - Sum(a);
left.push(a - offset_left);
right.push(l - offset_right);
} else if (!right.empty() && right.top() + offset_right < a) {
T r = right.replace_top(a - offset_right) + offset_right;
minimum += Sum(a) - Sum(r);
right.push(a - offset_right);
left.push(r - offset_left);
} else left.push(a - offset_left), right.push(a - offset_right);
}
void shift(T a, T b) {
assert(a <= b);
offset_left += a;
offset_right += b;
}
void shift(T a) { shift(a, a); }
Sum eval(T x) const {
Sum value = minimum;
for (T a : left.values())
if (x < a + offset_left) value += Sum(a + offset_left) - Sum(x);
for (T a : right.values())
if (a + offset_right < x) value += Sum(x) - Sum(a + offset_right);
return value;
}
void swap(SlopeTrick &other) noexcept {
left.swap(other.left);
right.swap(other.right);
std::swap(offset_left, other.offset_left);
std::swap(offset_right, other.offset_right);
std::swap(minimum, other.minimum);
}
void merge(SlopeTrick &other) {
if (this == &other) {
SlopeTrick copy(other);
merge(copy);
return;
}
if (size() < other.size()) swap(other);
minimum += other.minimum;
for (T a : other.left.values()) add_a_minus_x(a + other.offset_left);
for (T a : other.right.values()) add_x_minus_a(a + other.offset_right);
other = SlopeTrick{};
}
void merge(SlopeTrick &&other) { merge(other); }
};
#endif
#line 1 "data_structure/slope_trick.hpp"
#line 1 "components/d_ary_heap.hpp"
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <functional>
#include <utility>
#include <vector>
template <class T, class Compare = std::less<T>, std::size_t D = 2>
class DaryHeap {
static_assert(D >= 2);
std::vector<T> data;
[[no_unique_address]] Compare compare{};
void up(std::size_t i, T value) {
while (i) {
std::size_t p = (i - 1) / D;
if (!compare(data[p], value)) break;
data[i] = std::move(data[p]);
i = p;
}
data[i] = std::move(value);
}
void down(std::size_t i, T value) {
std::size_t n = data.size();
while (i < n / D + (n % D > 1)) {
std::size_t best = i * D + 1, end = n;
if constexpr (D <= std::size_t(-1) / 2) end = std::min(best + D, n);
for (std::size_t j = best + 1; j < end; ++j)
if (compare(data[best], data[j])) best = j;
if (!compare(value, data[best])) break;
data[i] = std::move(data[best]);
i = best;
}
data[i] = std::move(value);
}
public:
DaryHeap() = default;
explicit DaryHeap(Compare compare) : compare(std::move(compare)) {}
explicit DaryHeap(std::vector<T> values, Compare compare = {}) : data(std::move(values)), compare(std::move(compare)) {
for (std::size_t i = data.size() / D + (data.size() % D > 1); i--;) down(i, std::move(data[i]));
}
bool empty() const { return data.empty(); }
std::size_t size() const { return data.size(); }
void reserve(std::size_t n) { data.reserve(n); }
void clear() { data.clear(); }
const std::vector<T> &values() const { return data; }
decltype(auto) top() const {
assert(!empty());
return data.front();
}
void push(T value) {
data.push_back(std::move(value));
up(data.size() - 1, std::move(data.back()));
}
T replace_top(T value) {
assert(!empty());
T result = std::move(data.front());
down(0, std::move(value));
return result;
}
T push_pop(T value) {
if (empty() || !compare(value, data.front())) return value;
return replace_top(std::move(value));
}
T pop_value() {
assert(!empty());
T result = std::move(data.front());
if (data.size() == 1) data.pop_back();
else {
T value = std::move(data.back());
data.pop_back();
down(0, std::move(value));
}
return result;
}
void pop() { pop_value(); }
void swap(DaryHeap &other) noexcept(noexcept(std::swap(compare, other.compare))) {
data.swap(other.data);
std::swap(compare, other.compare);
}
};
#line 7 "data_structure/slope_trick.hpp"
#include <cstdint>
#line 9 "data_structure/slope_trick.hpp"
#include <optional>
#include <type_traits>
#line 12 "data_structure/slope_trick.hpp"
template <class T = std::int64_t, class Sum = T>
class SlopeTrick {
static_assert((std::is_arithmetic_v<T> && std::is_signed_v<T>) || std::is_same_v<T, __int128_t>);
static_assert((std::is_arithmetic_v<Sum> && std::is_signed_v<Sum>) || std::is_same_v<Sum, __int128_t>);
DaryHeap<T> left;
DaryHeap<T, std::greater<T>> right;
T offset_left = 0, offset_right = 0;
Sum minimum = 0;
public:
SlopeTrick() = default;
SlopeTrick(const SlopeTrick &) = default;
SlopeTrick &operator=(const SlopeTrick &) = default;
SlopeTrick(SlopeTrick &&other) noexcept : left(std::move(other.left)), right(std::move(other.right)), offset_left(std::exchange(other.offset_left, 0)), offset_right(std::exchange(other.offset_right, 0)), minimum(std::exchange(other.minimum, Sum(0))) {}
SlopeTrick &operator=(SlopeTrick &&other) noexcept {
if (this != &other) {
left = std::move(other.left);
right = std::move(other.right);
offset_left = std::exchange(other.offset_left, 0);
offset_right = std::exchange(other.offset_right, 0);
minimum = std::exchange(other.minimum, Sum(0));
}
return *this;
}
std::size_t size() const { return left.size() + right.size(); }
bool empty() const { return size() == 0; }
void reserve(std::size_t n) {
left.reserve(n);
right.reserve(n);
}
void clear_left() {
left.clear();
offset_left = 0;
}
void clear_right() {
right.clear();
offset_right = 0;
}
void clear() {
clear_left();
clear_right();
minimum = 0;
}
Sum min() const { return minimum; }
std::pair<std::optional<T>, std::optional<T>> argmin() const {
return {left.empty() ? std::nullopt : std::optional<T>(left.top() + offset_left), right.empty() ? std::nullopt : std::optional<T>(right.top() + offset_right)};
}
void add_all(Sum a) { minimum += a; }
void add_a_minus_x(T a) {
if (!right.empty() && right.top() + offset_right < a) {
T r = right.replace_top(a - offset_right) + offset_right;
minimum += Sum(a) - Sum(r);
left.push(r - offset_left);
} else left.push(a - offset_left);
}
void add_x_minus_a(T a) {
if (!left.empty() && a < left.top() + offset_left) {
T l = left.replace_top(a - offset_left) + offset_left;
minimum += Sum(l) - Sum(a);
right.push(l - offset_right);
} else right.push(a - offset_right);
}
void add_abs(T a) {
if (!left.empty() && a < left.top() + offset_left) {
T l = left.replace_top(a - offset_left) + offset_left;
minimum += Sum(l) - Sum(a);
left.push(a - offset_left);
right.push(l - offset_right);
} else if (!right.empty() && right.top() + offset_right < a) {
T r = right.replace_top(a - offset_right) + offset_right;
minimum += Sum(a) - Sum(r);
right.push(a - offset_right);
left.push(r - offset_left);
} else left.push(a - offset_left), right.push(a - offset_right);
}
void shift(T a, T b) {
assert(a <= b);
offset_left += a;
offset_right += b;
}
void shift(T a) { shift(a, a); }
Sum eval(T x) const {
Sum value = minimum;
for (T a : left.values())
if (x < a + offset_left) value += Sum(a + offset_left) - Sum(x);
for (T a : right.values())
if (a + offset_right < x) value += Sum(x) - Sum(a + offset_right);
return value;
}
void swap(SlopeTrick &other) noexcept {
left.swap(other.left);
right.swap(other.right);
std::swap(offset_left, other.offset_left);
std::swap(offset_right, other.offset_right);
std::swap(minimum, other.minimum);
}
void merge(SlopeTrick &other) {
if (this == &other) {
SlopeTrick copy(other);
merge(copy);
return;
}
if (size() < other.size()) swap(other);
minimum += other.minimum;
for (T a : other.left.values()) add_a_minus_x(a + other.offset_left);
for (T a : other.right.values()) add_x_minus_a(a + other.offset_right);
other = SlopeTrick{};
}
void merge(SlopeTrick &&other) { merge(other); }
};