AOJ 2785 Escape from the Hell

解法

とりあえず C[i] については無視して考えてみる。
最終日に使うドリンクを決めたとする。このドリンクを k とする。
すると、L - a[k] をできるだけ早く達成したいということになり、
当然 a[i] - b[i] が大きい方から貪欲に飲むのが最適である。
以降 a[i] - b[i] の降順でソートされているとする。

L - a[i] を達成する最小日数を求めるが、これは以下の2通りがある。

  • 場合1:ドリンク k より前のドリンクを飲んだ時点で達成
  • 場合2:ドリンク k 以降のドリンクを飲んだ時点で達成

場合1は簡単で、a[i] - b[i] の累積和(の max)が L - a[i] 以上となる位置を
二分探索で求めれば良い。

場合2のときは、0 ~ k - 1 までの a[i] - b[i] の総和を S として、
(a[i + 1] - b[i + 1]) + ... + (a[j] - b[j]) が L - a[i] - S 以上となる最小の j を探索する。
このために、区間 [l, r) に対し、[l, i) (l < i <= r) なる全ての i について考えたときの (a[l] - b[l]) + ... + (a[i - 1] - b[i - 1]) の最大値を求める必要があり、これは segment tree で実現できる。

こうして得られた最小日数の id を x としよう。
最後に c[i] について考える。これは a[i] - b[i] - c[i] の累積和について、先と同じように2通りに分けられる。

場合1は簡単であり、a[i] - b[i] - c[i] の x までの累積和(の min)が 0 より大きければ OK である。

場合2のときは、a[i] - b[i] - c[i] の k まで (exclusive k) の累積和が 0 より大きい、かつ (k + 1 から x までの "a[i] - b[i] - c[i - 1] の" 累積和の min) + (0 から k までの累積和)が 0 より大きければ OK である。
k を使えないので、インデックスが1つずれていることに注意する。
これも segment tree(max のときと同じ要領)で処理することができる。

あとはすべての k についてこれをやれば良い。
segment tree の上で二分探索をしたので O(n (logn)^2) で解ける。
ちゃんとやれば log はもう一つ落とせるが、まあ間に合うのでよし。

ソースコード

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

using ll = long long;
using pll = pair<ll, ll>;

constexpr ll inf = 1e18;

template<typename Monoid>
class segment_tree {
    using T = typename Monoid::type;

public:
    segment_tree(std::vector<T> const& init)
        : sz(init.size()), n(expand(init.size()))
    {
        dat.assign(n*2, Monoid::id());
        std::copy(begin(init), end(init), begin(dat) + n);
        for(int i = n - 1; i >= 0; --i) {
            dat[i] = Monoid::op(dat[i * 2], dat[i * 2 + 1]);
        }
    }

    segment_tree(int const n_, T const& init = Monoid::id())
        : segment_tree(std::vector<T>(n_, init))
    {}

    void update(int p, T val) {
        assert(0 <= p && p < sz);
        dat[p += n] = val;
        while(p /= 2) {
            dat[p] = Monoid::op(dat[p * 2], dat[p * 2 + 1]);
        }
    }

    // [l, r)
    T query(int l, int r) const {
        assert(0 <= l && l <= r && r <= sz);
        l += n, r += n;
        T res1 = Monoid::id(), res2 = Monoid::id();
        while(l != r) {
            if(l & 1) res1 = Monoid::op(res1, dat[l++]);
            if(r & 1) res2 = Monoid::op(dat[--r], res2);
            l /= 2, r /= 2;
        }
        return Monoid::op(res1, res2);
    }

private:
    int expand(int n_) const {
        assert(n_ >= 1);
        return n_ == 1 ? n_ : expand((n_ + 1) / 2) * 2;
    }

private:
    const int sz, n;
    std::vector<T> dat;
};

struct RSMQ {
    struct type {
        ll sum, mini, maxi;
        type() : sum(0), mini(inf), maxi(-inf) {}
        type(ll s, ll mi, ll ma) : sum(s), mini(mi), maxi(ma) {}
    };
    static type id() {
        return type{0, inf, -inf};
    }
    static type op(type const& l, type const& r) {
        if(l.mini == inf) return r;
        if(r.mini == inf) return l;
        return type(l.sum + r.sum,
                    min(l.mini, l.sum + r.mini),
                    max(l.maxi, l.sum + r.maxi));
    }
};

int main() {
    ll n, l; cin >> n >> l;
    vector<pll> drink(n);
    vector<ll> c(n);
    for(int i = 0; i < n; ++i) {
        cin >> drink[i].first >> drink[i].second;
    }
    for(int i = 0; i < n; ++i) {
        cin >> c[i];
    }
    sort(begin(drink), end(drink), [] (const pll& p1, const pll& p2) {
        return p1.first - p1.second > p2.first - p2.second;
    });

    // a[i] - b[i], a[i] - b[i] - c[i], a[i] - b[i] - c[i - 1]
    vector<RSMQ::type> dat1(n), dat2(n), dat3(n);
    for(int i = 0; i < n; ++i) {
        const ll a = drink[i].first, b = drink[i].second;
        dat1[i] = RSMQ::type{a - b, a - b, a - b};
        dat2[i] = RSMQ::type{a - b - c[i], a - b - c[i], a - b - c[i]};
        if(i != 0) {
            dat3[i] = RSMQ::type{a - b - c[i - 1], a - b - c[i - 1], a - b - c[i - 1]};
        }
    }
    segment_tree<RSMQ> seg1(dat1), seg2(dat2), seg3(dat3);

    int ans = n + 1;
    for(int i = 0; i < n; ++i) { // last use
        const ll r = l - drink[i].first;
        if(r <= 0) {
            ans = 1;
            break;
        }
        if(seg1.query(0, i).maxi < r) {
            if(seg2.query(0, i).mini <= 0) continue;
            int lb = i + 1, ub = n;
            const ll s = seg1.query(0, i).sum;
            while(ub - lb > 1) {
                const int mid = (ub + lb) >> 1;
                (seg1.query(i + 1, mid).maxi < r - s ? lb : ub) = mid;
            }
            if(seg1.query(i + 1, ub).maxi < r - s) continue;
            if(seg2.query(0, i).sum + seg3.query(i + 1, ub).mini <= 0) continue;
            ans = min(ans, ub);
        } else {
            int lb = 0, ub = i;
            while(ub - lb > 1) {
                const int mid = (lb + ub) >> 1;
                (seg1.query(0, mid).maxi < r ? lb : ub) = mid;
            }
            if(seg2.query(0, ub).mini <= 0) continue;
            ans = min(ans, ub + 1);
        }
    }

    cout << (ans == n + 1 ? -1 : ans) << endl;
}

感想

実装に失敗している感じもするが、なぜか全くバグらなかった。
ところで、こういうセグ木ははじめて書いた気がする。