mayoko’s diary

プロコンとかいろいろ。

JAG 夏合宿 Day3 F - Escape from the Hell

問題文は面白かったんですが実装はつらかったです。

解法

最後に A[i] 登るところ以外は, 明らかに D[j] = A[j] - B[j] が大きい順に使うのが最適です。この j をどこまで使うかで場合分けします。

j の最大値も j < i を満たす場合
あらかじめ sumD[i] = D[0] + D[1] + ... + D[i-1], sumC[i] = C[0] + ... + C[i-1] を計算して, sumD[ok] > sumC[ok] を満たす ok の最大値を覚えておきます。
A[i] + sumD[j] >= L を満たす j の最小値のうち, j <= ok となるものがあれば j+1 は解の候補です。

j の最大値が j > i を満たす場合
こっちが少しめんどくさいです。

この場合の上り方を見ると, 以下のようになっています。

D[0] D[1] D[2] ... D[i-1] D[i+1] ... D[j]
C[0] C[1] C[2] ... C[i-1] C[i] ... C[j-1]

よって, seg[i] = sumD[i] - sumC[i-1] というものを覚えておいて, min(seg[i+1], seg[i+2], ..., seg[j]) - D[i] > 0 でありかつ i-1 <= ok であれば良いです。

// セグメント木(RMQ 対応)
// update: k 番目の値を a に変更
// query: [l, r) の区間の最大値を求める
template<typename T>
struct ST {
    vector<T> seg;
    int size;
    ST(int n) {
        size = 1;
        while (size < n) size *= 2;
        seg.resize(2*size-1, numeric_limits<T>::max());
    }
    inline T merge(T x, T y) {
        return min(x, y);
    }
    void update(int k, T a) {
        k += size-1;
        seg[k] = a;
        while (k > 0) {
            k = (k-1)/2;
            seg[k] = merge(seg[k*2+1], seg[k*2+2]);
        }
    }
    T query(int a, int b, int k, int l, int r) {
        if (r <= a || b <= l) return numeric_limits<T>::max();
        if (a <= l && r <= b) return seg[k];
        T vl = query(a, b, k*2+1, l, (l+r)/2);
        T vr = query(a, b, k*2+2, (l+r)/2, r);
        return merge(vl, vr);
    }
    T query(int a, int b) {
        return query(a, b, 0, 0, size);
    }
};

const int INF = 1e9;

int main() {
    cin.tie(0);
    ios::sync_with_stdio(false);
    int N, L;
    cin >> N >> L;
    vector<pii> P(N);
    for (int i = 0; i < N; i++) {
        cin >> P[i].first >> P[i].second;
    }
    sort(P.begin(), P.end(), [](const pii& lhs, const pii& rhs){
        if (lhs.first-lhs.second == rhs.first-rhs.second) return lhs.first < rhs.first;
        return lhs.first-lhs.second > rhs.first-rhs.second;
    });
    vector<int> A(N), B(N), D(N);
    for (int i = 0; i < N; i++) {
        A[i] = P[i].first;
        B[i] = P[i].second;
        D[i] = A[i] - B[i];
    }
    vector<int> C(N);
    for (int i = 0; i < N; i++)
        cin >> C[i];
    vector<ll> sumD(N+1), sumC(N+1);
    for (int i = 0; i < N; i++) {
        sumD[i+1] = sumD[i] + D[i];
        sumC[i+1] = sumC[i] + C[i];
    }
    int ok = -1;
    for (int i = 1; i <= N; i++) {
        if (sumD[i] - sumC[i] > 0) {
            ok = i-1;
        } else break;
    }
    // cout << "A B" << endl;
    // for (int i = 0; i < N; i++) {
    //     cout << A[i] << " " << B[i] << endl;
    // }
    // cout << endl;
    // cout << "ok" << endl;
    // cout << ok << endl << endl;
    int ans = INF;
    {
        int maxi = 0;
        for (int i = N-1; i >= 0; i--) {
            maxi = max(maxi, A[i]);
            if (i-1 <= ok && maxi + sumD[i] >= L) ans = min(ans, i+1);
        }
    }
    ST<ll> seg(N+1);
    for (int i = 1; i <= N; i++) {
        seg.update(i, sumD[i] - sumC[i-1]);
    }
    for (int i = 0; i <= min(ok+1, N-2); i++) {
        // (low, high]
        int high = N-1, low = i;
        while (high - low > 1) {
            const int med = (high+low) / 2;
            if (sumD[med+1] + B[i] >= L) high = med;
            else low = med;
        }
        if (sumD[high+1] + B[i] < L) continue;
        //cout << i << " " << high << endl;
        //cout << seg.query(i+1, high+2) << endl;
        if (seg.query(i+1, high+2) > D[i]) ans = min(ans, high+1);
    }
    //cout << endl;
    if (ans == INF) ans = -1;
    cout << ans << endl;
    return 0;
}