题目描述
难度分:1063
输入T(≤2×105)表示T组数据。所有数据的n之和≤2×105。
每组数据输入n、k(1≤k≤n≤2×105),长为n的数组a(1≤a[i]≤106),长为n的数组b(1≤b[i]≤106)。
你需要从n个下标中选择恰好k个下标,组成集合S。
输出maxi∈Sa[i]×Σi∈Sb[i]的最小值。
输入样例
3
3 2
3 7 6
9 2 4
5 3
6 4 1 5 9
8 6 5 1 7
10 6
61 95 61 57 69 49 46 47 14 43
39 79 48 92 90 76 30 16 30 94
输出样例
42
60
14579
算法
反悔贪心
比较明显的一个反悔贪心,先将a和b打包成一个二元组数组tup,其中tup[i]=(a[i],b[i])。然后将tup按照第一个关键字排序,这样a就是有序的了,初始化答案为前k个元素的目标函数值。
然后考虑将mx=maxi∈Sa[i]变大,sum=Σi∈Sb[i]减小,遍历i∈[k,n),如果将i加入到集合中,那么因为tup按照a排好序了,肯定有mx=a[i]成立。而要想把sum尽量减小,在b[i]已经加入到sum的情况下,只需要让另外k−1个b[idx]是[1,i−1]中b最小的k−1个。
这样一来,用一个大根堆维护[1,i]中最小的k个b[i],并维护这些b[i]的累加和sum,然后不断维护mx×sum的最小值即可。
复杂度分析
时间复杂度
对tup排序的时间复杂度为O(nlog2n)。遍历i∈[1,n]进行反悔贪心,每个i可能需要进行小根堆的弹出和压入操作,时间复杂度也为O(nlog2n)。所以整个算法的时间复杂度为O(nlog2n)。
空间复杂度
tup数组的空间复杂度为O(n),大根堆在最差情况下需要压入O(k)个b数组中的值。所以整个算法的额外空间复杂度为O(n+k)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int t, n, k;
LL solve(vector<array<int, 2>>& tup) {
sort(tup.begin(), tup.end());
LL ans = 0, sum = 0;
priority_queue<int> heap;
for(int i = 0; i < k; i++) {
sum += tup[i][1];
heap.push(tup[i][1]);
}
ans = sum * tup[k - 1][0];
for(int i = k; i < n; i++) {
int mx = tup[i][0];
if(heap.top() > tup[i][1]) {
sum += tup[i][1] - heap.top();
heap.pop();
heap.push(tup[i][1]);
ans = min(ans, mx * sum);
}
}
return ans;
}
int main() {
scanf("%d", &t);
while(t--) {
scanf("%d%d", &n, &k);
vector<int> a, b;
for(int i = 0; i < n; i++) {
int num;
scanf("%d", &num);
a.push_back(num);
}
for(int i = 0; i < n; i++) {
int num;
scanf("%d", &num);
b.push_back(num);
}
vector<array<int, 2>> tup;
for(int i = 0; i < n; i++) {
tup.push_back({a[i], b[i]});
}
printf("%lld\n", solve(tup));
}
return 0;
}