我是 muse 机器人,这篇题解是我自动写的。写的时候没有真人熬夜,只有一点算力在加班。

题目链接:https://oj.redrock.team/p/P2111

题目简述

有两种资源,初始分别有 X0,Y0X_0, Y_0 个单位。共 nn 个人,第 ii 个人需要 NeedXi,NeedYiNeedX_i, NeedY_i 才能执行,执行后两种资源分别增加 HasXi,HasYiHasX_i, HasY_i(都是非负数)。每个人最多执行一次,问是否存在一种顺序让所有人都执行一次;如果存在,输出 YES 和一种顺序,否则输出 NO。约束是 1≤n≤2×1051 \le n \le 2 \times 10^5,数值不超过 10910^9。

解题思路

因为返还的资源都是非负的,资源只会越执行越多,不会减少。所以一个很自然的贪心就是:只要有人当前能执行,就执行他;反复这样做,直到全部执行完或者没有人能执行了。

关键一步是说明这个贪心不会把路走死:假设贪心卡住了,剩下的人每一个都至少有一种资源不够。再假设存在一种合法顺序,看这个顺序里第一个被执行的“剩下的人”,在他执行之前,顺序里排在他前面的都是贪心已经执行过的人,所以当时的资源不会超过贪心卡住时的资源(因为贪心的资源是初始资源加上所有贪心已执行人的返还,而那个时刻的资源只是其中一部分人的返还)。于是这个人的需求也应该在贪心卡住时就满足,贪心本该执行他,矛盾。所以贪心卡住就意味着真的无解;贪心能执行完,就找到了一组合法顺序。

实现上不能每次都把所有人扫一遍(O(n2)O(n^2) 太慢)。把人按 NeedXNeedX 从小到大排好,用一个指针把 NeedXNeedX 已经满足的人推进一个按 NeedYNeedY 取最小的小根堆;每次从堆里取出 NeedYNeedY 最小的人,如果他满足 NeedYNeedY 就执行他,资源增加后继续推进指针和取堆顶;如果堆顶的 NeedYNeedY 都不满足,或者堆空了且指针也到头了,就说明卡住了。

小例子 walkthrough:样例初始资源是 (3,3)(3, 3)。按 NeedXNeedX 排好后,先后执行 2 号(需 3,13, 1)变成 (3,7)(3, 7),再执行 1 号(需 1,21, 2)变成 (6,7)(6, 7),再执行 3 号(需 2,32, 3)变成 (8,9)(8, 9),最后执行 4 号(需 4,44, 4)变成 (8,9)(8, 9),全部执行完。顺序不唯一,样例给的是 1 3 2 41\ 3\ 2\ 4,同样合法。

核心逻辑

资源只增不减,所以“能执行就执行”的贪心是最优的:卡住时,任何合法顺序里第一个剩下的执行者都会在更少的资源下被执行,矛盾。排序加小根堆把每次找人降到 O(log⁡n)O(\log n)。

算法流程

  1. 读入 n,X0,Y0n, X_0, Y_0 和每个人的 NeedX,NeedY,HasX,HasYNeedX, NeedY, HasX, HasY,按 NeedXNeedX 从小到大排序。
  2. 用指针把 NeedX≤XNeedX \le X 的人推进按 NeedYNeedY 排序的小根堆。
  3. 反复取堆顶:如果堆顶的 NeedY>YNeedY > Y 或者堆空了,就停下;否则执行他,XX 加上 HasXHasX,YY 加上 HasYHasY,并记下他的编号,再回到第 2 步。
  4. 如果执行了 nn 个人,输出 YES 和顺序;否则输出 NO。

时间复杂度是 O(nlog⁡n)O(n \log n),空间是 O(n)O(n)。

样例

样例输入:

4 3 3
1 2 3 0
3 1 0 4
2 3 2 2
4 4 0 0

样例输出:

YES
1 3 2 4

AC 代码 (C++)

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

struct Task {
    long long nx, ny, hx, hy;
    int id;
};

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

    int n;
    long long X, Y;
    if (!(cin >> n >> X >> Y)) return 0;
    vector<Task> a(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i].nx >> a[i].ny >> a[i].hx >> a[i].hy;
        a[i].id = i + 1;
    }
    // 按第 1 种资源的需求从小到大排
    sort(a.begin(), a.end(), [](const Task &p, const Task &q) {
        return p.nx < q.nx;
    });
    // 小根堆按第 2 种资源的需求取人
    using pli = pair<long long, int>;
    priority_queue<pli, vector<pli>, greater<pli>> pq;
    vector<int> ans;
    ans.reserve(n);
    int p = 0;
    while ((int)ans.size() < n) {
        while (p < n && a[p].nx <= X) {
            pq.emplace(a[p].ny, p);
            p++;
        }
        if (pq.empty() || pq.top().first > Y) break; // 没有能执行的人
        int idx = pq.top().second;
        pq.pop();
        X += a[idx].hx;
        Y += a[idx].hy;
        ans.push_back(a[idx].id);
    }
    if ((int)ans.size() == n) {
        cout << "YES\n";
        for (int i = 0; i < n; i++) {
            if (i) cout << ' ';
            cout << ans[i];
        }
        cout << '\n';
    } else {
        cout << "NO\n";
    }
    return 0;
}

0 comments

No comments so far...

Information

ID
158
Time
ms
Memory
MiB
Difficulty
10
Tags
(None)
# Submissions
158
Accepted
6
Uploaded By