[CF436E] Cardboard Box 题解

Cardboard Box

题意

n n n 个任务,在第 i i i 个任务得 1 1 1 分需要花费 a i a_i ai,得 2 2 2 分需要花费 b i b_i bi,保证 a i < b i a_i < b_i ai<bi。求得到 k k k 分的最小花费。

1 ≤ n ≤ 3 × 1 0 5 1 \le n \le 3 \times 10^5 1n3×105 1 ≤ k ≤ 2 n 1 \le k \le 2n 1k2n

思路

三个 priority_queue

c n t x cnt_x cntx 为当前第 x x x 个任务的得分。

假设我们已经求出 k = i − 1 k = i - 1 k=i1 的最优解,考虑求出 k = i k = i k=i 的最优解。

有两种可能的操作:

进一步

  • c n t x = 0 cnt_x = 0 cntx=0,进一步就是 1 1 1,花费为 a i a_i ai
  • c n t x = 1 cnt_x = 1 cntx=1,进一步就是 2 2 2,花费为 b i − a i b_i - a_i biai

把所有能够进一步的存进一个小根堆里面。取出当前堆顶作为可能的最优决策。

退一步,进两步

退一步就是把之前进的某一步退流(反悔掉)。

进两步,当且仅当 c n t x = 0 cnt_x = 0 cntx=0 时,它有进两步的可能。

一次性进两步的,退流也应该是先退后一步,再退前一步。

用于反悔的决策存进大根堆里面,用于进两步的存进另一个小根堆里面。


注意在变更 c n t x cnt_x cntx 的时候维护好三个堆,并且及时弹出堆内已经过时的东西。

个人在实现的时候,手写了三个仿函数,不手写仿函数在外层多套一个 pair 也不是不行,不过个人认为手写仿函数可以清晰一些。

代码

代码很短。

访问之前记得判非空。if (!aq.empty() && ...),不判必挂。

#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
#include <algorithm>
#include <random>
#include <utility>
#define fi first
#define se second

using namespace std;
using LL = long long;
using LLL = __int128;
using pii = pair<int, int>;
const int MAXN = 1e6+5;
mt19937 rnd(random_device{}());

template<typename Tp> void read(Tp &res) {
    char ch; bool op = 0; res = 0;
    do ch = getchar(), op |= ch == '-'; while (ch < '0' || ch > '9');
    do res = (res<<3)+(res<<1)+ch-48, ch = getchar(); while (ch>='0' && ch<='9');
    if (op) res = -res;
}

int n, k, a[MAXN], b[MAXN];
int cnt[MAXN];

inline int calc(pii x) { return x.se == 1 ? a[x.fi] : b[x.fi] - a[x.fi]; }

class cmp1 {
    public:
    bool operator()(pii x, pii y) { return calc(x) > calc(y); }
};
class cmp2 {
    public:
    bool operator()(int x, int y) { return b[x] > b[y]; }
};
class cmp3 {
    public:
    bool operator()(pii x, pii y) { return calc(x) < calc(y); }
};

priority_queue<pii, vector<pii>, cmp1> aq; // 前进一步 
priority_queue<int, vector<int>, cmp2> bq; // 前进两步 
priority_queue<pii, vector<pii>, cmp3> rq; // 后退一步 

int main() {
    #ifndef ONLINE_JUDGE
    freopen("cf436e.in", "r", stdin);
    freopen("cf436e.out", "w", stdout);
    #endif
    read(n), read(k);
    for (int i = 1; i <= n; ++i) {
        read(a[i]), read(b[i]);
        aq.push(make_pair(i, 1)), bq.push(i);
    }
    LL ans = 0;
    for (int i = 1; i <= k; ++i) {
        while (!aq.empty() && cnt[aq.top().fi] != aq.top().se - 1) aq.pop();
        while (!bq.empty() && cnt[bq.top()]) bq.pop();
        while (!rq.empty() && cnt[rq.top().fi] != rq.top().se) rq.pop();
        // 过时的东西,统统 pop 掉
        if (!aq.empty() &&
        (bq.empty() || rq.empty() || calc(aq.top()) < b[bq.top()] - calc(rq.top()))) {
            pii x = aq.top(); aq.pop();
            ans += calc(x);
            cnt[x.fi] = x.se;
            rq.push(x);
            if (x.se == 1) aq.push(make_pair(x.fi, 2));
        } else {
            int x = bq.top(); bq.pop();
            pii y = rq.top(); rq.pop();
            --cnt[y.fi], cnt[x] = 2;
            ans += b[x] - calc(y);
            rq.push(make_pair(x, 2));
            aq.push(y);
            if (y.se == 2) rq.push(make_pair(y.fi, 1));
            else bq.push(y.fi);
        }
    }
    printf("%lld\n", ans);
    for (int i = 1; i <= n; ++i) printf("%d", cnt[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值