これは典型らしいので、このセグメント木の使い方は今後押さえる!
問題概要
正の整数からなる数列 が与えられる。
一般に、数列 に対して、
とする。まず の値を求めて、その後、次の
回のクエリに答えよ。
【クエリ】
整数 が与えられるので、数列
の
の値を
に書き換える。そして、その後
の値を出力せよ。
制約
考えたこと
数列 を固定したときの、
の求め方をまず考える。この手の問題によくあることとして、数列全体に等差数列を足したり引いたりすると、目的関数が分かりやすく記述できることがよくある。そうすると、次の 2 つの値のうちの大きい方の値に一致することがわかる。
- max が min の右側にある場合:数列
を考えたとき、
(
) の最大値
- max が min の左側にある場合:数列
を考えたとき、
(
) の最大値
後者は前者と同様に扱えるので、結局前者のみ考えることとする。
前者は、なんとセグメント木に乗る!!! セグメント木のモノイドを
- 区間内の最小値
mi
- 区間内の最大値
ma
(答えの更新に必要) - 区間内の「右側 - 左側」の最大値
ans
で定義する。このとき、モノイド l
, r
の二項演算は次のように定義できる。
auto func = [&](const Node &l, const Node &r) -> Node { return Node(min(l.mi, r.mi), max(l.ma, r.ma), max({l.ans, r.ans, r.ma - l.mi})); };
こうして、各クエリには次のように対処すればよいとわかった。
- 更新「
」:上記のセグメント木の 1 点更新
- 取得「
の値を求める」:上記のセグメント木の全体区間の値を取得
全体で、 の計算量の解法となる。
コード
#include <bits/stdc++.h> using namespace std; // Segment Tree template<class Monoid> struct SegmentTree { using Func = function<Monoid(Monoid, Monoid)>; // core member int N; Func OP; Monoid IDENTITY; // inner data int log, offset; vector<Monoid> dat; // constructor SegmentTree() {} SegmentTree(int n, const Func &op, const Monoid &identity) { init(n, op, identity); } SegmentTree(const vector<Monoid> &v, const Func &op, const Monoid &identity) { init(v, op, identity); } void init(int n, const Func &op, const Monoid &identity) { N = n; OP = op; IDENTITY = identity; log = 0, offset = 1; while (offset < N) ++log, offset <<= 1; dat.assign(offset * 2, IDENTITY); } void init(const vector<Monoid> &v, const Func &op, const Monoid &identity) { init((int)v.size(), op, identity); build(v); } void pull(int k) { dat[k] = OP(dat[k * 2], dat[k * 2 + 1]); } void build(const vector<Monoid> &v) { assert(N == (int)v.size()); for (int i = 0; i < N; ++i) dat[i + offset] = v[i]; for (int k = offset - 1; k > 0; --k) pull(k); } void clear() { dat.assign(dat.size(), IDENTITY); } int size() const { return N; } Monoid operator [] (int i) const { return dat[i + offset]; } // update A[i], i is 0-indexed, O(log N) void set(int i, const Monoid &v) { assert(0 <= i && i < N); int k = i + offset; dat[k] = v; while (k >>= 1) pull(k); } // get [l, r), l and r are 0-indexed, O(log N) Monoid prod(int l, int r) { assert(0 <= l && l <= r && r <= N); Monoid val_left = IDENTITY, val_right = IDENTITY; l += offset, r += offset; for (; l < r; l >>= 1, r >>= 1) { if (l & 1) val_left = OP(val_left, dat[l++]); if (r & 1) val_right = OP(dat[--r], val_right); } return OP(val_left, val_right); } Monoid all_prod() { return dat[1]; } // get max r such that f(v) = True (v = prod(l, r)), O(log N) // f(IDENTITY) need to be True int max_right(const function<bool(Monoid)> f, int l = 0) { if (l == N) return N; l += offset; Monoid sum = IDENTITY; do { while (l % 2 == 0) l >>= 1; if (!f(OP(sum, dat[l]))) { while (l < offset) { l = l * 2; if (f(OP(sum, dat[l]))) { sum = OP(sum, dat[l]); ++l; } } return l - offset; } sum = OP(sum, dat[l]); ++l; } while ((l & -l) != l); // stop if l = 2^e return N; } // get min l that f(get(l, r)) = True (0-indexed), O(log N) // f(IDENTITY) need to be True int min_left(const function<bool(Monoid)> f, int r = -1) { if (r == 0) return 0; if (r == -1) r = N; r += offset; Monoid sum = IDENTITY; do { --r; while (r > 1 && (r % 2)) r >>= 1; if (!f(OP(dat[r], sum))) { while (r < offset) { r = r * 2 + 1; if (f(OP(dat[r], sum))) { sum = OP(dat[r], sum); --r; } } return r + 1 - offset; } sum = OP(dat[r], sum); } while ((r & -r) != r); return 0; } // debug friend ostream& operator << (ostream &s, const SegmentTree &seg) { for (int i = 0; i < (int)seg.size(); ++i) { s << seg[i]; if (i != (int)seg.size() - 1) s << " "; } return s; } }; const long long INF = 1LL << 60; struct Node { long long mi, ma, ans; Node(){} Node(long long mi, long long ma, long long ans) : mi(mi), ma(ma), ans(ans) {} friend ostream& operator << (ostream &s, const Node &n) { return s << "(" << n.mi << "," << n.ma << "," << n.ans << ")"; } }; void solve() { long long N, Q, p, x; cin >> N >> Q; vector<long long> a(N); for (int i = 0; i < N; i++) cin >> a[i]; vector<Node> left(N), right(N); for (int i = 0; i < N; i++) { left[i] = Node(a[i] - i, a[i] - i, 0); right[N-i-1] = Node(a[i] + i, a[i] + i, 0); } const Node identity = Node(INF, -INF, -INF); auto func = [&](const Node &l, const Node &r) -> Node { return Node(min(l.mi, r.mi), max(l.ma, r.ma), max({l.ans, r.ans, r.ma - l.mi})); }; SegmentTree<Node> segleft(left, func, identity), segright(right, func, identity); cout << max(segleft.all_prod().ans, segright.all_prod().ans) << endl; while (Q--) { cin >> p >> x, p--; long long pl = p, pr = N-1-p, add = x-a[p]; a[p] = x; long long leftval = segleft[pl].mi + add; long long rightval = segright[pr].mi + add; segleft.set(pl, Node(leftval, leftval, 0)); segright.set(pr, Node(rightval, rightval, 0)); cout << max(segleft.all_prod().ans, segright.all_prod().ans) << endl; } } int main() { int T; cin >> T; while (T--) solve(); }