← 返回题目页

A1520 [BJ2020.C2] 装备升级

标程

// A1520 [BJ2020.C2] 装备升级 —— 增广贪心
//
// 每件装备可以升 0/1/2 级:第 i 件从 0 级升到 1 级花 w1_i 枚金币,
// 从 1 级升到 2 级再花 w2_i 枚金币。目标:总共升级 m 次,花费最少。
//
// 做法:从「一件都没升」的状态出发,一次一次地「增广」一个升级次数。
// 每一步在四种候选方案里挑代价最小的那个执行:
//   A:把一件 0 级装备升到 1 级,代价 w1(取 0 级装备里最小的 w1);
//   B:把一件 1 级装备升到 2 级,代价 w2(取 1 级装备里最小的 w2);
//   C:把一件 1 级装备降回 0 级(退回 w1),同时把一件 0 级装备一次升到 2 级(花 w1+w2);
//   D:把一件 2 级装备降回 1 级(退回 w2),同时把一件 0 级装备一次升到 2 级。
// C、D 是「反悔」操作:当某件装备的第二次升级特别便宜时,
// 与其先升别的装备再补它,不如把这件装备直接一次升到 2 级更划算。
//
// 每个候选的最优取值用四个堆维护(懒删除),总复杂度 O(n log n + m log n)。

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

typedef long long ll;
const ll INF = (1LL << 62);

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

    int n, m;
    cin >> n >> m;
    vector<ll> w1(n), w2(n);
    for (int i = 0; i < n; i++) cin >> w1[i] >> w2[i];

    vector<int> lvl(n, 0);
    // 0 级装备:w1 最小、w1+w2 最小
    priority_queue<pair<ll, int>, vector<pair<ll, int>>, greater<pair<ll, int>>> hEmptyW1, hEmptyC;
    // 1 级装备:w2 最小、w1 最大
    priority_queue<pair<ll, int>, vector<pair<ll, int>>, greater<pair<ll, int>>> hOneW2;
    priority_queue<pair<ll, int>> hOneW1Max;
    // 2 级装备:w2 最大
    priority_queue<pair<ll, int>> hTwoW2Max;

    auto setLevel = [&](int i, int v) {
        lvl[i] = v;
        if (v == 0) {
            hEmptyW1.push({w1[i], i});
            hEmptyC.push({w1[i] + w2[i], i});
        } else if (v == 1) {
            hOneW2.push({w2[i], i});
            hOneW1Max.push({w1[i], i});
        } else {
            hTwoW2Max.push({w2[i], i});
        }
    };
    for (int i = 0; i < n; i++) setLevel(i, 0);

    ll total = 0;
    for (int step = 0; step < m; step++) {
        // 懒删除:把已经不在该集合里的堆顶弹掉
        while (!hEmptyW1.empty() && lvl[hEmptyW1.top().second] != 0) hEmptyW1.pop();
        while (!hEmptyC.empty() && lvl[hEmptyC.top().second] != 0) hEmptyC.pop();
        while (!hOneW2.empty() && lvl[hOneW2.top().second] != 1) hOneW2.pop();
        while (!hOneW1Max.empty() && lvl[hOneW1Max.top().second] != 1) hOneW1Max.pop();
        while (!hTwoW2Max.empty() && lvl[hTwoW2Max.top().second] != 2) hTwoW2Max.pop();

        ll best = INF;
        int kind = -1, j = -1, i = -1;
        // A:0 级装备升 1 级
        if (!hEmptyW1.empty()) {
            best = hEmptyW1.top().first;
            kind = 0;
            j = hEmptyW1.top().second;
        }
        // B:1 级装备升 2 级
        if (!hOneW2.empty() && hOneW2.top().first < best) {
            best = hOneW2.top().first;
            kind = 1;
            j = hOneW2.top().second;
        }
        // C:1 级降回 0 级,另一件 0 级一次升到 2 级
        if (!hEmptyC.empty() && !hOneW1Max.empty()) {
            ll v = hEmptyC.top().first - hOneW1Max.top().first;
            if (v < best) {
                best = v;
                kind = 2;
                j = hEmptyC.top().second;
                i = hOneW1Max.top().second;
            }
        }
        // D:2 级降回 1 级,另一件 0 级一次升到 2 级
        if (!hEmptyC.empty() && !hTwoW2Max.empty()) {
            ll v = hEmptyC.top().first - hTwoW2Max.top().first;
            if (v < best) {
                best = v;
                kind = 3;
                j = hEmptyC.top().second;
                i = hTwoW2Max.top().second;
            }
        }

        if (kind == 0) {
            setLevel(j, 1);
            total += w1[j];
        } else if (kind == 1) {
            setLevel(j, 2);
            total += w2[j];
        } else if (kind == 2) {
            setLevel(j, 2);
            setLevel(i, 0);
            total += w1[j] + w2[j] - w1[i];
        } else {
            setLevel(j, 2);
            setLevel(i, 1);
            total += w1[j] + w2[j] - w2[i];
        }
    }

    cout << total << '\n';
    return 0;
}

动画演示