H - Mancala 2 解説 by MMNMM

すこし定数倍の軽い方法

この問題は、数列 \(A\) に対して区間 \((A _ L,A _ {L+1},\ldots,A _ R)\) への一様な加算と、一点 \(A _ x\) へのアクセスが管理できればよいです。

ここで、数列 \(D=(D _ 0,D _ 1,\ldots,D _ {N-1})\) を \(\displaystyle A _ i=\sum _ {j=0} ^ iD _ j\) を満たすものとし、\(D\) を管理することにします。
すると、上の \(2\) 種類のクエリは \(2\) 点 \(D _ L,D _ {R+1}\) へそれぞれ \(+k,-k\) することと、 prefix sum \(\displaystyle\sum _ {j=0} ^ xD _ j\) を求めることと言い換えられます。

これは(遅延評価の対応をしない)セグメント木や Binary Indexed Tree によって処理することができます。

公式解説の解法を遅延評価セグメント木で実装した場合と比較し、計算量の漸近的なオーダーは時間 \(O((N+M)\log N)\) および空間 \(O(N)\) となり変わりませんが、時間計算量や空間計算量をより削減することができます。

実装例は以下のようになります。

#include <iostream>
#include <atcoder/fenwicktree>

int main() {
    using namespace std;
    unsigned N, M;
    cin >> N >> M;

    atcoder::fenwick_tree<unsigned long> D(N);
    { // BIT を初期化
        unsigned long A, prev_A{};
        for(unsigned i = 0; i < N; ++i){
            cin >> A;
            // ∑_j D[j] = A[i] とするには D[i] = A[i] - A[i - 1] とすればよい
            D.add(i, A - prev_A);
            prev_A = A;
        }
    }

    for(unsigned i = 0; i < M; ++i){
        unsigned B;
        cin >> B;
        auto x{D.sum(0, B + 1)}; // A[B] の値は ∑_{0 <= j <= B} D[j]

        // A[B] の値を 0 にする
        D.add(B, -x);
        if(B + 1 < N)D.add(B + 1, x);

        // 全体には足さないぶんの処理
        D.add((B + 1) % N, 1); // 配り始めから末尾まで +1
        D.add((B + 1 + x) % N, -1); // 配り終わりから末尾まで -1
        x += (B + 1) % N - (B + 1 + x) % N; // 足したぶんを引いておく

        // 全体に x / N を足す
        D.add(0, x / N);
    }

    for(unsigned i = 1; i <= N; ++i)
        cout << D.sum(0, i) << " ";
    cout << endl;

    return 0;
}

投稿日時:
最終更新: