Li-Chao (Segment) Tree with Lazy Propagation
概要
以下のクエリを処理できる遅延評価 Li Chao (Segment) Tree の実装
-
全ての \(a_i\) の初期値は \(+\infty\)
-
線分追加: \(a_l, ..., a_{r-1}\) の各 \(a_i\) について \(\min(a_i, a \cdot i + b)\) に更新
-
区間加算: \(a_l, ..., a_{r-1}\) の各 \(a_i\) について \(a_i + c\) に更新
-
区間最小: \(a_l, ..., a_{r-1}\) の中の最小値を求める
実装説明
線分追加 は従来の Li-Chao Tree と同様。(pushdown等は行う)
区間加算クエリでは、ノードを探索している間に現在のノードに存在する線分 \(f(x) = ax + b\) の一部分を更新する必要がある場合は、ノードから \(f(x)\) を取り出し左右の子ノードのそれぞれに \(f(x)\) を線分追加することで線分を分割する。
一回の区間加算クエリでは、ノードが持つ \(f(x)\) を子ノードへの線分追加する処理を高々 \(O(\log N)\) 回行うことになり、一回の線分追加は \(O(\log N)\) であるため、一回の区間加算クエリは \(O(\log^2 N)\) となる。
計算量
区間 \([0, N)\) に対する各クエリについて
-
線分追加: \(O(\log^2 N)\)
-
区間加算: \(O(\log^2 N)\)
-
区間最小: \(O(\log N)\)
実装
#include<algorithm>
using ll = long long;
using namespace std;
const static ll inf = 1e18L;
class LiChaoTree {
struct Line {
ll a, b;
Line(ll a, ll b) : a(a), b(b) {}
Line() : a(0), b(0) {}
inline ll f(ll x) const {
return a*x + b;
}
Line& operator+=(ll c) {
if(b >= inf || c >= inf) {
a = 0; b = inf;
} else {
b += c;
}
return *this;
}
bool operator==(const Line &ln) const {
return a == ln.a && b == ln.b;
}
};
const Line line_inf = Line{0, inf};
const Line line_zero = Line{0, 0};
struct Node {
Node *left, *right;
Line line;
ll lazy;
ll v_min;
Node(Line line) : left(nullptr), right(nullptr), line(line), lazy(0), v_min(inf) {}
};
Node *root;
ll xl, xr;
inline bool compare(Line &l0, Line &l1, ll x) const {
return l0.f(x) <= l1.f(x);
}
inline void _update_node(Node *nd, ll l, ll r) {
nd->v_min = min(nd->line.f(l), nd->line.f(r-1));
if(nd->left) nd->v_min = min(nd->v_min, nd->left->v_min);
if(nd->right) nd->v_min = min(nd->v_min, nd->right->v_min);
}
inline void _push_line(Node *nd, ll l, ll r) {
if(nd == nullptr || nd->line == line_inf) return;
ll m = (l + r) / 2;
nd->left = _add_line(nd->left, nd->line, l, m);
nd->right = _add_line(nd->right, nd->line, m, r);
nd->line = line_inf;
}
inline void _add_lazy(Node *nd, ll &c) {
if(nd == nullptr) return;
nd->line += c;
nd->lazy = (nd->lazy + c >= inf ? inf : nd->lazy + c);
nd->v_min = (nd->v_min + c >= inf ? inf : nd->v_min + c);
}
inline void _push_lazy(Node *nd, ll l, ll r) {
if(nd == nullptr || nd->lazy == 0) return;
_add_lazy(nd->left, nd->lazy);
_add_lazy(nd->right, nd->lazy);
nd->lazy = 0;
}
Node* _add_line(Node *nd, Line line, ll l, ll r) {
if(l == r) return nullptr;
ll m = (l + r) / 2;
if(nd == nullptr) {
nd = new Node(line);
} else {
_push_lazy(nd, l, r);
bool left = compare(line, nd->line, l);
bool mid = compare(line, nd->line, m);
bool right = compare(line, nd->line, r);
if(mid) {
swap(nd->line, line);
}
if(r-l > 1 && left != right) {
if(left != mid) {
nd->left = _add_line(nd->left, line, l, m);
} else {
nd->right = _add_line(nd->right, line, m, r);
}
}
}
_update_node(nd, l, r);
return nd;
}
Node* _add_val(ll a, ll b, Node *nd, ll c, ll l, ll r) {
if(r <= a || b <= l) {
return nd;
}
if(a <= l && r <= b) {
_add_lazy(nd, c);
return nd;
}
_push_lazy(nd, l, r);
_push_line(nd, l, r);
if(nd == nullptr) {
nd = new Node(line_inf);
}
ll m = (l + r) / 2;
nd->left = _add_val(a, b, nd->left, c, l, m);
nd->right = _add_val(a, b, nd->right, c, m, r);
_update_node(nd, l, r);
return nd;
}
Node* _add_segment_line(ll a, ll b, Node *nd, Line &line, ll l, ll r) {
if(r <= a || b <= l) {
return nd;
}
if(a <= l && r <= b) {
return _add_line(nd, line, l, r);
}
_push_lazy(nd, l, r);
if(nd == nullptr) {
nd = new Node(line_inf);
}
ll m = (l + r) / 2;
nd->left = _add_segment_line(a, b, nd->left, line, l, m);
nd->right = _add_segment_line(a, b, nd->right, line, m, r);
_update_node(nd, l, r);
return nd;
}
ll _query_min(ll a, ll b, Node *nd, ll l, ll r) {
if(nd == nullptr || b <= l || r <= a) {
return inf;
}
if(a <= l && r <= b) {
return nd->v_min;
}
_push_lazy(nd, l, r);
ll m = (l + r) / 2;
ll lv = _query_min(a, b, nd->left, l, m);
ll rv = _query_min(a, b, nd->right, m, r);
_update_node(nd, l, r);
return min(
min(lv, rv),
min(nd->line.f(max(l, a)), nd->line.f(min(r, b)-1))
);
}
public:
LiChaoTree(ll xl, ll xr) : xl(xl), xr(xr), root(nullptr) {}
// a_i <- min(a_i, a*i + b) for i in [xl, xr)
void add_line(ll a, ll b) {
Line line = Line{a, b};
root = _add_line(root, line, xl, xr);
}
// a_i <- min(a_i, a*i + b) for i in [l, r)
void add_segment_line(ll a, ll b, ll l, ll r) {
Line line = Line{a, b};
root = _add_segment_line(l, r, root, line, xl, xr);
}
// a_i <- a_i + c for i in [l, r)
void add_val(ll c, ll l, ll r) {
root = _add_val(l, r, root, c, xl, xr);
}
// a_i <- +∞ for i in [l, r)
void reset_val(ll l, ll r) {
root = _add_val(l, r, root, inf, xl, xr);
}
// get min_{i in [l, r)} a_i
ll query_min(ll l, ll r) {
return _query_min(l, r, root, xl, xr);
}
};
Verified
-
AtCoder: "AtCoder Beginner Contest 177 - F問題: I hate Shortest Path Problem": source (C++, 824ms)