G - Add and Multiply Queries Editorial by MMNMM


区間 \([L,R)\) に対して、\(v=x\) で入ったときの最終的な \(v\) の値を \(f _ {[L,R)}(x)\) と定めます。

すると、\(f _ {[L,R)}(x)\) はいくつかの一次関数 \(ax+b\) の最大値として表すことができます。

これを適切に管理することでもこの問題を解くことができます。 具体的には、\(0\leq x\leq 10 ^ {18}\) かつ \(0\leq f _ {[L,R)}(x)\leq 10 ^ {18}\) において最大値をとりうる一次関数だけを管理すると、どの区間 \([L,R)\) に対してもたかだか \(\lceil\log _ 210 ^ {18}\rceil\) 個の一次関数を管理すればよいことになります。

あとは、関数の合成 \(f _ {[L,R)}(x)=f _ {[M,R)}\circ f _ {[L,M)}(x)\) を高速に計算できればよいです。 これは、一次関数を \(a\) の昇順で持っておくことで \(O(\)一次関数の個数の合計\()\) 時間で合成が計算できます。

詳細な実装は以下の実装例を参考にしてください。

#include <iostream>
#include <algorithm>
#include <vector>
#include <ranges>
#include <atcoder/segtree>

int main() {
    using namespace std;
    // 一次関数の max の inf 未満の部分を管理する
    constexpr unsigned long inf = 1000000000000000000;
    class piecewise_linear_convex {
        // 一次関数
        struct linear {
            unsigned long a, b;

            constexpr unsigned long operator()(unsigned long x) const {
                if ((inf - b) / a < x)
                    return inf;
                return x * a + b;
            }
        };

        vector<pair<unsigned long, linear>> piece;
    public:
        [[nodiscard]] constexpr piecewise_linear_convex() : piece{{0, {1, 0}}} {}

        [[nodiscard]] constexpr explicit piecewise_linear_convex(vector<pair<unsigned long, linear>> &&_p) : piece{std::move(_p)} {}

        [[nodiscard]] constexpr explicit piecewise_linear_convex(const vector<pair<unsigned long, linear>> &_p) : piece{_p} {}

        // 代入したときの値を求める
        constexpr unsigned long operator()(unsigned long x) const {
            if (empty(piece))
                return inf;
            return prev(ranges::upper_bound(piece, x, less<>{}, &pair<unsigned long, linear>::first))->second(x);
        }

        // (A, B) の単一要素に対する値
        static constexpr piecewise_linear_convex from_single_state(unsigned long a, unsigned long b) {
            if (b == 1)
                return piecewise_linear_convex{{{0, {1, a}}}};
            return piecewise_linear_convex{{{0, {1, a}}, {(a + b - 1) / (b - 1), {b, 0}}}};
        }

        // 関数 [L, M) と [M, R) の合成
        constexpr piecewise_linear_convex operator+(const piecewise_linear_convex &rhs) const {
            const auto &p_lhs{this->piece};
            const auto &p_rhs{rhs.piece};
            // 0 を入れたときに inf になるなら空を返す
            if (empty(p_lhs) || rhs(p_lhs.front().second(0)) == inf)
                return piecewise_linear_convex{{}};

            const auto &N_lhs{size(p_lhs)}, &N_rhs{size(p_rhs)};
            vector<pair<unsigned long, linear>> result;
            // 値 x を代入したときに最大値をとる一次関数の index を求める
            const auto find_bound{[](const auto &seq, unsigned long key, unsigned start) -> unsigned {
                const auto N{size(seq)};
                for (unsigned i{start}; i < N; ++i)
                    if (seq[i].first > key)
                        return i;
                return N;
            }};
            // 結果に一次関数を追加する(最大値を取る区間の昇順に追加する)
            auto add_line{[prev_x{inf}](auto &&seq, unsigned long begin, linear func) mutable {
                if (prev_x == begin)
                    seq.back().second = func;
                else
                    seq.emplace_back(begin, func);
                prev_x = begin;
            }};

            for (unsigned i_lhs{}, i_rhs{find_bound(p_rhs, p_lhs[i_lhs].second(p_lhs[i_lhs].first), 0)}; i_lhs < N_lhs; ++i_lhs) {
                const auto &[pa, pb]{p_lhs[i_lhs].second};
                // [L, M) の一次関数が切り替わる境界に対応する境界
                {
                    // inf になったら処理を終了
                    if (p_rhs[i_rhs - 1].second(p_lhs[i_lhs].first) == inf)
                        return piecewise_linear_convex{std::move(result)};
                    const auto &[ra, rb]{p_rhs[i_rhs - 1].second};
                    add_line(result, p_lhs[i_lhs].first, linear{pa * ra, pb * ra + rb});
                }

                // [M, R) の一次関数が切り替わる境界に対応する境界
                const auto next_i_rhs{i_lhs + 1 < N_lhs ? find_bound(p_rhs, p_lhs[i_lhs + 1].second(p_lhs[i_lhs + 1].first), i_rhs) : N_rhs};
                for (unsigned i{i_rhs}; i < next_i_rhs; ++i) {
                    const auto begins{p_rhs[i].first};
                    const auto x{(max(begins + pa, pb + 1) - pb - 1) / pa};
                    // inf になったら処理を終了
                    if (p_rhs[i].second(x) == inf)
                        return piecewise_linear_convex{std::move(result)};
                    const auto &[ra, rb]{p_rhs[i].second};
                    add_line(result, x, linear{pa * ra, pb * ra + rb});
                }
            }
            return piecewise_linear_convex{std::move(result)};
        }
    };

    using segment_tree_type = atcoder::segtree<piecewise_linear_convex, plus<>{}, [] { return piecewise_linear_convex{}; }>;

    unsigned N;
    cin >> N;
    vector<pair<unsigned long, unsigned long>> coefficients(N);
    for (auto &&[A, _]: coefficients)
        cin >> A;
    for (auto &&[_, B]: coefficients)
        cin >> B;

    // 組 (A, B) に対応する一次関数列を作ってセグメント木を構築
    segment_tree_type segment_tree{
            [](auto &&range) {
                return vector<ranges::range_value_t<decltype(range)>>(ranges::begin(range), ranges::end(range));
            }(
                coefficients
                | views::transform([](const auto &coef) { return piecewise_linear_convex::from_single_state(coef.first, coef.second); }
            ))};

    unsigned Q;
    cin >> Q;
    for (unsigned q{}; q < Q; ++q) {
        unsigned type, a, b;
        cin >> type >> a >> b;
        if (type == 1) {
            --a;
            coefficients[a].first = b;
            segment_tree.set(a, piecewise_linear_convex::from_single_state(coefficients[a].first, coefficients[a].second));
        } else if (type == 2) {
            --a;
            coefficients[a].second = b;
            segment_tree.set(a, piecewise_linear_convex::from_single_state(coefficients[a].first, coefficients[a].second));
        } else
            cout << segment_tree.prod(a - 1, b)(0) << endl;
    }

    return 0;
}

posted:
last update: