【反悔贪心】AtCoder ABC376 E. Max × Sum
前言
反悔贪心是贪心算法的一种改进或扩展策略。贪心算法是指仅考虑每步最优,从而达到全局最优。在传统贪心算法的基础上,反悔贪心允许算法在一定条件下进行回溯或调整。简言之就是采取骑驴找马的策略,在别无选择的情况下,先选择当前最优解,当有更好的选择的时候,将之前最糟糕的选择替换为新的选择。
题目
https://atcoder.jp/contests/abc376/tasks/abc376_e
题意
输入一个正整数 \(T(1 \leq T \leq 2 \times 10^5)\),代表共有 \(T\) 组测试用例。
对于每个测试用例,输入两个正整数 \(n, k(1 \leq k \leq n \leq 2 \times 10^5)\),随后输入一个长为 \(n\) 的数组 \(a\),再输入一个长为 \(n\) 的数组 \(b\)。
选出恰好 \(k\) 个下标,不妨记 \(mx\) 为 \(a\) 所选子数组中的最大值,\(sum\) 为 \(b\) 所选子数组全部元素之和。你要做的是,最小化 \(mx \times sum\) 的结果。
题解
将数组 \(a\) 从小到大排序(注意要对下标排序,因为数组 \(a, b\) 的位置是对应的),当从小到大遍历时,\(a\) 的元素都必定是子数组中的最大值,而之前已经遍历到的位置对最大值并不会产生任何影响,因此 \(a\) 之前所选的任何元素都是可选可不选。而 \(b\) 数组除了当前下标位置的元素必须选外,在当前下标之前的元素也都是可选可不选。想使得 \(b\) 的子数组元素和最小化,自然是选出当前下标位置之前最小的 \(k - 1\) 个元素。具体地,可以使用大根堆维护 \(b\) 的前 \(k\) 小元素。
参考代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
constexpr int N = 2e5 + 7;
int T, n, k;
int a[N], b[N], c[N];
void solve() {
ll ans = 0LL, sum = 0LL;
cin >> n >> k;
iota(c, c + n, 0);
for (int i = 0; i < n; ++ i) cin >> a[i];
for (int i = 0; i < n; ++ i) cin >> b[i];
sort(c, c + n, [&](const int &i, const int &j) {
return a[i] != a[j] ? a[i] < a[j] : b[i] < b[j];
});
priority_queue<int> pq;
for (int i = 0; i < k; ++ i) {
sum += b[c[i]];
pq.push(b[c[i]]);
}
ans = a[c[k - 1]] * sum;
for (int i = k; i < n; ++ i) {
if (pq.top() > b[c[i]]) {
sum += b[c[i]] - pq.top();
pq.pop();
pq.emplace(b[c[i]]);
ans = min(ans, sum * a[c[i]]);
} else {
ans = min(ans, (sum - pq.top() + b[c[i]]) * a[c[i]]);
}
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
cin >> T;
while (T --) {
solve();
}
return 0;
}
浙公网安备 33010602011771号