公式

G - Retroactive Range Chmax 解説 by en_translator


First, we consider applying and canceling \(A\leftarrow\max\lbrace A,x\rbrace\) against a single integer, instead of a sequence.

Since the operations commute, one can manage the multiset of the values of \(x\) for the operations not canceled so far in a heap (so-called “erasable priority queue” or a balanced binary search tree) to solve it fast.

This idea can be adopted to the original problem too.

As in a segment tree, consider \(O(N)\) segments contained in \(\lbrack0,N)\) and written in the form of \(\lbrack i2 ^ k,(i+1)2 ^ k)\ (i,k\) are integers\()\). Then, for any segment \(\lbrack l,r)\),

  • updating \(A _ i\leftarrow\max\lbrace A _ i,x\rbrace\) and
  • canceling it

can be realized as addition and removal of \(x\) against \(O(\log N)\) multisets.

The response to a retrieval query of \(A _ i\) can also be found by inspecting the maximum values of the \(O(\log N)\) multisets containing \(A _ i\), and then finding the maximum among them.

The time complexity is \(O((N+Q)\log N\log Q)\), using appropriate data structures.

The sample code is as follows.

#include <iostream>
#include <vector>
#include <queue>
#include <utility>
#include <tuple>

int main() {
    using namespace std;

    // Operation against erasable priority queue
    using erasable_priority_queue = pair<priority_queue<unsigned>, priority_queue<unsigned>>;
    // Function that adds x
    const auto add{[](unsigned x) { return [x](auto &&pq) { return pq.first.push(x); }; }};
    // Function that deletes x
    const auto erase{[](unsigned x) { return [x](auto &&pq) { return pq.second.push(x); }; }};
    // Function that returns the maximum value in pq (or 0 if empty)
    const auto top_or_zero{[](auto &&pq) {
        while (!empty(pq.second) && pq.first.top() == pq.second.top()) {
            pq.first.pop();
            pq.second.pop();
        }
        return empty(pq.first) ? 0 : pq.first.top();
    }};

    unsigned N;
    cin >> N;

    vector<erasable_priority_queue> intervals(2 * N);
    for (unsigned i{}, a; i < N; ++i) {
        cin >> a;
        add(a)(intervals[i + N]); // Insert a to the segment corresponding to [i, i+1)
    }

    // Split [l, r) into O(log N) segments, and call `callback(I)` for each segment I
    const auto range_update{[N, &intervals](unsigned l, unsigned r, const auto &callback) {
        l += N;
        r += N;
        while (l != r) {
            if (l & 1)
                callback(intervals[l++]);
            if (r & 1)
                callback(intervals[--r]);
            l /= 2;
            r /= 2;
        }
    }};

    // i を含む O(log N) 個の区間を列挙し、各区間 I について mapping(I) を呼び出した結果 m を aggregate(ret, m) で集計する
    // Enumerate O(log N) segments containing i, and use aggregate(ret, m) to summarize the result m of calling mapping(I) for each segment I
    const auto point_access{[N, &intervals](unsigned i, const auto &mapping, const auto &aggregate) {
        i += N;
        unsigned ret{};
        while (i) {
            ret = aggregate(ret, mapping(intervals[i]));
            i /= 2;
        }
        return ret;
    }};

    unsigned Q;
    cin >> Q;
    vector<tuple<unsigned, unsigned, unsigned>> instructions(Q);
    for (auto&&[l, r, x] : instructions) {
        unsigned type;
        cin >> type;
        if (type == 1) {
            cin >> l >> r >> x;
            --l;
            range_update(l, r, add(x)); // Insert x to segment [l, r)
        } else if (type == 2) {
            cin >> x;
            const auto&[l_erased, r_erased, x_erased]{instructions[x - 1]};
            range_update(l_erased, r_erased, erase(x_erased)); // Remove x_erased from segment [l_erased, r_erased)
        } else {
            cin >> x;
            // Print the maximum value
            cout << point_access(x - 1, top_or_zero, [](auto a, auto b) { return max(a, b); }) << endl;
        }
    }
    return 0;
}

投稿日時:
最終更新: