题目链接
比较 mini 的模拟赛考到了,想了半小时 DP 终于在结束前 5min 成功想出正解但是并没有写完。
解题思路
首先我们考虑什么时候可以拿取寿司。容易发现因为我们必须在 \(n\) 分钟结束时吃完所有拿取的寿司,那么最后 \(k\) 个寿司不能拿取;同时如果到目前的时间减去拿寿司所用时间小于等手里寿司的数量,此时也是不能拿取的。
然后我们发现如果我们直接用上面的条件进行正向枚举并判断是错误的,因为虽然总时间是够的,但是不能保证每个寿司在拿取后有充足的时间吃掉它。我们修改一下条件:拿取该寿司后的剩余时间减去拿取寿司的时间大于等于吃掉所有寿司的时间时可以拿取寿司。这样就很明显需要我们倒序枚举。
由于题目要求我们的美味值最大,我们在符合条件的范围内优先拿取寿司,当发现时间不够了就对比一下之前拿取的最小的美味度和现在的谁更大,我们丢掉更小的那个。这很显然就是反悔贪心啦。
整合一下思路。倒序枚举寿司盘 \(i(1 \leq i \le n - k)\),记录拿取次数 \(cnt\),开一个小根堆维护已经拿取的寿司,我们先拿取寿司,当 \((n - i + 1) - cnt \lt cnt \times k\) 时说明时间超限,此时我们对比堆顶和当前美味值,留下美味值更高的那个,统计答案即可。
关于一个实现细节
注意到在我贴的代码中并没有判断队列是否为空,因为此时有 \(cnt = 0\),所以肯定会被判断为时间充裕从而直接拿取寿司。换言之,当上面的判断成立时肯定有 \(cnt\) 不为 \(0\),那么队列也不为空,就不需要判断了。
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;const int MN = 2e5 + 3;
int n, k, ans;
int d[MN];priority_queue<int, vector<int>, greater<int> > q;inline void fir() {while (!q.empty()) q.pop();ans = 0;
}
void solve() {cin >> n >> k;fir();for (int i = 1; i <= n; i++) cin >> d[i];int tmp = 0; // 拿取次数for (int i = n; i >= 1; i--) {if (i > n - k) continue;tmp++; // 先拿上if ((n - i + 1) - tmp < tmp * k) {int x = q.top();if (x < d[i]) {q.pop();q.push(d[i]);ans += d[i] - x;}tmp--; // 扔掉原来那个}else {q.push(d[i]);ans += d[i];}}cout << ans << "\n";
}signed main() {ios::sync_with_stdio(0);cin.tie(0), cout.tie(0);int T;cin >> T;while (T--) solve();return 0;
}