結果

問題 No.2465 Dilated Water Simulation
コンテスト
ユーザー butsurizuki
提出日時 2026-07-22 02:36:30
言語 C++23
(gcc 15.2.0 + boost 1.90.0)
コンパイル:
g++-15 -O2 -lm -std=c++23 -Wuninitialized -DONLINE_JUDGE -o a.out _filename_
実行:
./a.out
結果
AC  
実行時間 122 ms / 2,000 ms
+ 48µs
コード長 10,667 bytes
記録
記録タグの例:
初AC ショートコード 純ショートコード 純主流ショートコード 最速実行時間
コンパイル時間 2,366 ms
コンパイル使用メモリ 349,812 KB
実行使用メモリ 43,904 KB
最終ジャッジ日時 2026-07-22 02:36:37
合計ジャッジ時間 7,064 ms
ジャッジサーバーID
(参考情報)
judge2_0 / judge3_0
このコードへのチャレンジ
(要ログイン)
ファイルパターン 結果
sample AC * 3
other AC * 25
権限があれば一括ダウンロードができます

ソースコード

diff #
raw source code

#include <bits/stdc++.h>
using namespace std;

using i64 = long long;
using i128 = __int128_t;

static const i128 INF128 = (i128(1) << 120);

struct Line {
    i64 slope = 0;
    i128 intercept = INF128;

    i128 eval(i64 x) const {
        return (i128)slope * x + intercept;
    }
};

// 直線追加を rollback できる Li Chao Tree
struct RollbackLiChao {
    int x_left;
    int x_right;

    const vector<Line>* lines;

    // 各ノードに格納されている直線番号
    vector<int> tree;

    // (変更したノード, 変更前の直線番号)
    vector<pair<int, int>> history;

    RollbackLiChao(
        int x_left_,
        int x_right_,
        const vector<Line>* lines_,
        int reserve_lines = 0
    )
        : x_left(x_left_),
          x_right(x_right_),
          lines(lines_),
          tree(4 * max(1, x_right_ - x_left_ + 1) + 16, -1) {

        if (reserve_lines > 0) {
            history.reserve((size_t)reserve_lines * 20);
        }
    }

    size_t snapshot() const {
        return history.size();
    }

    void rollback(size_t snapshot_size) {
        while (history.size() > snapshot_size) {
            auto [node, old_line] = history.back();
            history.pop_back();
            tree[node] = old_line;
        }
    }

    void assign_node(int node, int line_id) {
        history.push_back({node, tree[node]});
        tree[node] = line_id;
    }

    void add_line_rec(int node, int left, int right, int line_id) {
        if (tree[node] == -1) {
            assign_node(node, line_id);
            return;
        }

        int middle = (left + right) / 2;
        int current_id = tree[node];

        bool better_at_left =
            (*lines)[line_id].eval(left)
            < (*lines)[current_id].eval(left);

        bool better_at_middle =
            (*lines)[line_id].eval(middle)
            < (*lines)[current_id].eval(middle);

        if (better_at_middle) {
            assign_node(node, line_id);
            line_id = current_id;
        }

        if (left == right) {
            return;
        }

        if (better_at_left != better_at_middle) {
            add_line_rec(
                node * 2,
                left,
                middle,
                line_id
            );
        } else {
            add_line_rec(
                node * 2 + 1,
                middle + 1,
                right,
                line_id
            );
        }
    }

    void add_line(int line_id) {
        add_line_rec(
            1,
            x_left,
            x_right,
            line_id
        );
    }

    i128 query_rec(
        int node,
        int left,
        int right,
        int x
    ) const {
        i128 answer = INF128;

        if (tree[node] != -1) {
            answer = (*lines)[tree[node]].eval(x);
        }

        if (left == right) {
            return answer;
        }

        int middle = (left + right) / 2;

        if (x <= middle) {
            answer = min(
                answer,
                query_rec(
                    node * 2,
                    left,
                    middle,
                    x
                )
            );
        } else {
            answer = min(
                answer,
                query_rec(
                    node * 2 + 1,
                    middle + 1,
                    right,
                    x
                )
            );
        }

        return answer;
    }

    i128 query(int x) const {
        return query_rec(
            1,
            x_left,
            x_right,
            x
        );
    }
};

// LIFO の push/pop と直線最小値を管理するスタック
struct HullStack {
    RollbackLiChao hull;

    vector<int> line_ids;
    vector<size_t> snapshots;

    HullStack(
        int x_left,
        int x_right,
        const vector<Line>* lines,
        int reserve_lines = 0
    )
        : hull(
            x_left,
            x_right,
            lines,
            reserve_lines
        ) {

        if (reserve_lines > 0) {
            line_ids.reserve(reserve_lines);
            snapshots.reserve(reserve_lines);
        }
    }

    bool empty() const {
        return line_ids.empty();
    }

    void push(int line_id) {
        snapshots.push_back(hull.snapshot());
        line_ids.push_back(line_id);
        hull.add_line(line_id);
    }

    int pop() {
        int line_id = line_ids.back();

        hull.rollback(snapshots.back());

        line_ids.pop_back();
        snapshots.pop_back();

        return line_id;
    }

    i128 query(int x) const {
        if (empty()) {
            return INF128;
        }
        return hull.query(x);
    }
};

// 二つのスタックでキューを作る
struct HullQueue {
    HullStack input_stack;
    HullStack output_stack;

    HullQueue(
        int x_left,
        int x_right,
        const vector<Line>* lines,
        int reserve_lines = 0
    )
        : input_stack(
            x_left,
            x_right,
            lines,
            reserve_lines
        ),
          output_stack(
            x_left,
            x_right,
            lines,
            reserve_lines
        ) {}

    void push_back(int line_id) {
        input_stack.push(line_id);
    }

    void move_to_output() {
        if (!output_stack.empty()) {
            return;
        }

        while (!input_stack.empty()) {
            output_stack.push(input_stack.pop());
        }
    }

    void pop_front() {
        move_to_output();
        output_stack.pop();
    }

    i128 query(int x) const {
        return min(
            input_stack.query(x),
            output_stack.query(x)
        );
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int L;
    cin >> L;

    vector<i64> capacity(L);
    for (i64& value : capacity) {
        cin >> value;
    }

    i64 N;
    cin >> N;

    const i64 full_cycles = N / L;
    const int remainder = (int)(N % L);

    const i64 minimum_capacity =
        *min_element(
            capacity.begin(),
            capacity.end()
        );

    vector<i64> reduced_capacity(L);

    for (int i = 0; i < L; ++i) {
        reduced_capacity[i] =
            capacity[i] - minimum_capacity;
    }

    vector<i64> answer(L, 0);

    const i64 total_reduced_water =
        reduced_capacity[0];

    // 一周も行わない場合、または残りの水が存在しない場合
    if (full_cycles == 0 || total_reduced_water == 0) {
        answer[0] = total_reduced_water;
    } else {
        // 最初の容量 0 の容器
        // 関係する容器は 0,...,M-1
        int M = 1;

        while (
            M < L &&
            reduced_capacity[M] > 0
        ) {
            ++M;
        }

        if (M == 1) {
            // 直後が容量 0 なので残りの水は動かない
            answer[0] = total_reduced_water;
        } else {
            vector<i128> prefix_sum(M + 1, 0);

            for (int i = 0; i < M; ++i) {
                prefix_sum[i + 1] =
                    prefix_sum[i]
                    + reduced_capacity[i];
            }

            // r=1,...,M-2 に対応する直線
            vector<Line> lines(M);

            for (int r = 1; r <= M - 2; ++r) {
                lines[r].slope =
                    reduced_capacity[r];

                lines[r].intercept =
                    prefix_sum[r]
                    + (
                        (i128)full_cycles - r
                    ) * reduced_capacity[r];
            }

            const int max_x = max(1, M - 2);

            HullQueue queue(
                1,
                max_x,
                &lines,
                M
            );

            // l=1 のとき有効な r は
            // 1,...,min(M-2,K)
            i64 initial_right =
                min<i64>(
                    M - 2,
                    full_cycles
                );

            for (
                int r = 1;
                r <= initial_right;
                ++r
            ) {
                queue.push_back(r);
            }

            // F[i] = 辺 i -> i+1 の累積移動量
            vector<i64> cumulative_flow(M - 1, 0);

            // W による上限
            i128 prefix_minimum =
                total_reduced_water;

            for (int l = 1; l <= M - 1; ++l) {
                i128 best_cost = INF128;

                // 終点 M-1 に到達しない候補
                if (l <= M - 2) {
                    best_cost =
                        queue.query(l)
                        - prefix_sum[l];
                }

                // 終点 M-1 へ直接到達する候補
                if (
                    (i64)M - l
                    <= full_cycles
                ) {
                    best_cost = min(
                        best_cost,
                        prefix_sum[M]
                        - prefix_sum[l]
                    );
                }

                prefix_minimum = min(
                    prefix_minimum,
                    best_cost
                );

                // prefix_minimum <= W <= 1e9
                cumulative_flow[l - 1] =
                    (i64)prefix_minimum;

                if (l <= M - 2) {
                    // 次の l では r=l が範囲外になる
                    queue.pop_front();

                    // 新しく r=l+K が範囲に入る
                    i64 new_r =
                        (i64)l + full_cycles;

                    if (new_r <= M - 2) {
                        queue.push_back(
                            (int)new_r
                        );
                    }
                }
            }

            // 累積移動量から各容器の水量を復元
            answer[0] =
                total_reduced_water
                - cumulative_flow[0];

            for (int i = 1; i <= M - 2; ++i) {
                answer[i] =
                    cumulative_flow[i - 1]
                    - cumulative_flow[i];
            }

            answer[M - 1] =
                cumulative_flow[M - 2];
        }
    }

    // 共通部分 m は完全な周の境界では容器 0 にある
    answer[0] += minimum_capacity;

    // 残り N mod L 回を直接シミュレーション
    for (int i = 0; i < remainder; ++i) {
        i64 moved = min(
            answer[i],
            capacity[i + 1] - answer[i + 1]
        );

        answer[i] -= moved;
        answer[i + 1] += moved;
    }

    for (int i = 0; i < L; ++i) {
        if (i != 0) {
            cout << ' ';
        }
        cout << answer[i];
    }

    cout << '\n';
    return 0;
}
0