区間に等差数列を作用させる遅延セグメントツリー
たまに見る割にまとまった記事がない気がしたので
仕組み
次のような更新を目標にします。
- をそれぞれ で更新する。
これはセグ木のノードに最小値、最大値、総和、左端/右端のindexを持たせることで実現できます。
ノードのマージはそれぞれのminやmaxをそのまま取ればよいです。
- なら左端が新しい最小値となり、右端が新しい最大値となる
- なら左端が新しい最大値となり、右端が新しい最小値となる
ことに注意することで最小値/最大値を計算できます。
総和は等差数列の和なので更新後の総和を計算して上書きすればよいです。
ノードのマージと作用素の作用が入れ替え可能なので(略)、無事遅延セグ木に乗せることができました。
添え字がずれていますが、 の代わりに で更新することで冒頭で述べた変更と一致します。
コード
ACLのlazy_segtreeを継承しています。初項の変更を自動的に行います。
using ll = long long; // 区間min & 区間max & 区間sum & 区間等差更新 namespace arithmetic { const ll LINF = ll(4e18); const int INF = int(1e9) + 10; struct S { ll min, max, sum; int l, r; }; struct F { ll a, b; }; S op(S s, S t) { return {min(s.min, t.min), max(s.max, t.max), s.sum + t.sum, min(s.l, t.l), max(s.r, t.r)}; } S e() { return {LINF, -LINF, 0, INF, -INF}; } S mapping(F f, S s) { if (f.a == LINF) return s; if (f.a >= 0) return {f.a * s.l + f.b, f.a * (s.r - 1) + f.b, (f.a * (s.l + s.r - 1) + f.b * 2) * (s.r - s.l) / 2, s.l, s.r}; else return {f.a * (s.r - 1) + f.b, f.a * s.l + f.b, (f.a * (s.l + s.r - 1) + f.b * 2) * (s.r - s.l) / 2, s.l, s.r}; } F composition(F f, F g) { return f.a == LINF ? g : f; } F id() { return {LINF, LINF}; } using lazy_segtype = atcoder::lazy_segtree<S, op, e, F, mapping, composition, id>; struct lazy_segtree_arithmetic : lazy_segtype { using lazy_segtype::lazy_segtype; lazy_segtree_arithmetic(int n) { vector<S> sv(n); for (int i = 0; i < n; i++) sv[i] = {0, 0, 0, i, i + 1}; (*this) = lazy_segtree_arithmetic(sv); } lazy_segtree_arithmetic(vector<ll> v) { vector<S> sv(v.size()); for (int i = 0; i < v.size(); i++) sv[i] = {v[i], v[i], v[i], i, i + 1}; (*this) = lazy_segtree_arithmetic(sv); } // f = (a, b) // [l, r) を b, a+b, 2a+b, ... で更新 void apply(int l, int r, F f) { lazy_segtype::apply(l, r, {f.a, f.b - f.a * get(l).l}); } void apply(int p, F f) { apply(p, {f.a, f.b - f.a * get(p).l}); } }; } // namespace arithmetic using namespace arithmetic;
Verify
ちょうどいい問題がなかったので作りました。
Range Arithmetic Query (easy) | MojaCoder
Range Arithmetic Query (hard) | MojaCoder
愚直解と照らし合わせているので変なバグはないと思われますが、おかしい点があればご一報ください。
'Range Arithmetic Query (hard)'のstoqさんの提出 | MojaCoder
使用例
ABC177-F I hate Shortest Path Problem
F - I hate Shortest Path Problem
これが解けなかったのがきっかけで作りました。貼るだけで解けます。
int main() { int h, w; cin >> h >> w; lazy_segtree_arithmetic seg(w); for (int i = 0; i < h; i++) { int l, r; cin >> l >> r; l--; ll x = (l > 0 ? seg.get(l - 1).min : LINF); seg.apply(l, r, {1, x + 1}); ll ans = seg.all_prod().min; cout << (ans >= LINF ? -1 : ans + i + 1) << "\n"; } }
Submission #25254062 - AtCoder Beginner Contest 177
他に使える問題を知っている方がいれば教えてください。
区間等差数列加算の遅延セグ木
関連して、区間に等差数列を足す遅延セグ木も考えられます。Min, Max の取得は難しいですが、総和は同様の考え方で実装できます。
using ll = long long; // 区間sum & 区間等差加算 namespace arithmetic_add { const ll LINF = ll(4e18); const int INF = int(1e9) + 10; struct S { ll sum; int l, r; }; struct F { ll a, b; }; S op(S s, S t) { return {s.sum + t.sum, min(s.l, t.l), max(s.r, t.r)}; } S e() { return {0, INF, -INF}; } S mapping(F f, S s) { return {s.sum + (f.a * (s.l + s.r - 1) + f.b * 2) * (s.r - s.l) / 2, s.l, s.r}; } F composition(F f, F g) { return {f.a + g.a, f.b + g.b}; } F id() { return {0, 0}; } using lazy_segtype = atcoder::lazy_segtree<S, op, e, F, mapping, composition, id>; struct lazy_segtree_arithmetic : lazy_segtype { using lazy_segtype::lazy_segtype; lazy_segtree_arithmetic(int n) { vector<S> sv(n); for (int i = 0; i < n; i++) sv[i] = {0, i, i + 1}; (*this) = lazy_segtree_arithmetic(sv); } lazy_segtree_arithmetic(vector<ll> v) { vector<S> sv(v.size()); for (int i = 0; i < v.size(); i++) sv[i] = {v[i], i, i + 1}; (*this) = lazy_segtree_arithmetic(sv); } // f = (a, b) // [l, r) に b, a+b, 2a+b, ... を加算 void apply(int l, int r, F f) { lazy_segtype::apply(l, r, {f.a, f.b - f.a * get(l).l}); } void apply(int p, F f) { apply(p, {f.a, f.b - f.a * get(p).l}); } }; } // namespace arithmetic_add using namespace arithmetic_add;