【第10回】累積和といもす法

累積和といもす法を使えるようになろう

Homeブログ一覧【第10回】累積和といもす法

累積和とは

累積和については、過去の記事を参考にしてください。
累積和で高速処理をしてみよう

いもす法

いもす法とは、累積和のアルゴリズムを多次元,多次数に拡張したものです。

引用元: https://imoz.jp/algorithms/imos_method.html

具体的には更新範囲の境界部分のみを変更し、最終的に累積和を取ることによって正しい答えを得る方法です。

例題

文章で説明するのは難しいので問題を利用して説明します。

解説

まず、すべての時間に対応した配列を作り、その時間に利用されるお湯の量PiP_iを足していき、その量がWWを超えないかどうかを確認する、という方法が考えられます。

#include <bits/stdc++.h>
using namespace std;
int main() {
    long long n, w;
    cin >> n >> w;
    vector<long long> s(n), t(n), p(n), check(200001);
    for ( int i = 0; i < n; i++ ) {
        cin >> s.at(i) >> t.at(i) >> p.at(i);
        for ( int j = s.at(i); j < t.at(i); j++ ) {
            check.at(j) += p.at(i);
        }
    }
    for ( int i = 0; i < check.size(); i++ ) {
        if ( check.at(i) > w ) {
            cout << "No" << endl;
            return 0;
        }
    }
    cout << "Yes" << endl;
}

しかし、TiSiT_i - S_i は最大で2×1052 \times 10^5であるので計算量は最大で4×10104 \times 10^{10}となり、TLEしてしまいます。
そこで、check.at(s.at(i))p.at(i)を足し、check.at(t.at(i))からp.at(i)を引き、最終的に累積和を取ることで答えを得られます。
例えば入力が

2 6
3 5 3
1 6 2

であった場合、以下のようにします。 いもす法 この例では66を超えることがないのでYESを出力します。
最終的なコードは以下のようになります。

#include <bits/stdc++.h>
using namespace std;
int main() {
    long long n, w;
    cin >> n >> w;
    vector<long long> s(n), t(n), p(n), check(200001);
    for ( int i = 0; i < n; i++ ) {
        cin >> s.at(i) >> t.at(i) >> p.at(i);
        check.at(s.at(i)) += p.at(i);
        check.at(t.at(i)) -= p.at(i);
    }
    for ( int i = 1; i < check.size(); i++ ) {
        check.at(i) += check.at(i-1);
    }
    for ( int i = 0; i < check.size(); i++ ) {
        if ( check.at(i) > w ) {
            cout << "No" << endl;
            return 0;
        }
    }
    cout << "Yes" << endl;
}

これの書き方であれば計算量は多くとも2×1052 \times 10^5であり、間に合います。 この解き方がいもす法と呼ばれています。
また、いもす法は1次元でなくとも利用することができます。

練習問題

最後の問題は2次元いもす法を使います。