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 1≤n≤3×105, 1 ≤ k ≤ 2 n 1 \le k \le 2n 1≤k≤2n。
思路
三个 priority_queue
。
记 c n t x cnt_x cntx 为当前第 x x x 个任务的得分。
假设我们已经求出 k = i − 1 k = i - 1 k=i−1 的最优解,考虑求出 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 bi−ai。
把所有能够进一步的存进一个小根堆里面。取出当前堆顶作为可能的最优决策。
退一步,进两步
退一步就是把之前进的某一步退流(反悔掉)。
进两步,当且仅当 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;
}