题目描述
难度分:1600
输入n,k(1≤k≤n≤3×105)和n个物品,每个物品输入两个属性值t[i]和b[i],范围 [1,106]。
从这n个物品中选出至多k个物品,输出这k个物品的sum(t)×min(b)的最大值。
输入样例1
4 3
4 7
15 1
3 6
6 8
输出样例1
78
输入样例2
5 3
12 31
112 4
100 100
13 55
55 50
输出样例2
10000
算法
反悔贪心
这种题目的做法非常经典,一般要求最大值那就对其中的一个属性降序排列,先贪这个属性的最大值。然后试图减小这个属性,增加另一个属性,从而增加目标函数的值。
初始化答案ans=0,选择的物品t属性之和为sum,先对物品数组playlist按照b属性降序排列,然后遍历playlist,当遍历到playlist[i]时,playlist[i].b就是目标函数中的min(b)。先无脑选择这个物品,将playlist[i].t放入小根堆,然后有如下两种情况:
- 如果此时堆中的元素个数(就是选择的物品)不超过k,那就直接更新此时的目标函数最大值。
- 否则就需要换出一个贡献最小的物品,由于在选择playlist[i]时min(b)已经减小了,因此我们希望换出去的playlist[i]的t属性最小,这样sum那一项不会有很大损失,弹出堆顶的t即可,此时先更新sum=sum−heap.top()+playlist[i].t,然后用sum×playlist[i].b来更新答案。之所以playlist[i].b不需要变是因为如果小根堆pop掉的不是新加入的playlist[i].t,那显然min(b)=playlist[i].b,弹出堆顶后的答案就是(sum−heap.top()+playlist[i].t)×playlist[i];如果小根堆pop掉的是新加入的playlist[i].t,那就相当于啥也没干,而啥也没干的sum(t)×min(b)是否能更新答案已经在遍历到playlist[i]之前考虑过了,此时的sum(t)×playlist[i].b是无法更新ans的,不用管。
这样遍历一遍数组playlist就能得到最大的目标函数。
复杂度分析
时间复杂度
遍历一遍数组,每遍历到一个物品时,会往堆中压入元素,时间复杂度是O(log2n)的,因此整体算法的时间复杂度为O(nlog2n)。
空间复杂度
除去输入数据的空间,空间的瓶颈就是堆的消耗。最差情况下堆中会存入O(n)级别的元素数量,因此额外空间复杂度为O(n)。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long LL;
const int N = 300010;
int n, k;
struct node {
LL t, b;
bool operator<(const node other) const {
return b > other.b;
}
} playlist[N];
int main() {
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; i++) {
scanf("%lld%lld", &playlist[i].t, &playlist[i].b);
}
sort(playlist + 1, playlist + n + 1);
priority_queue<int, vector<int>, greater<int>> heap;
LL ans = 0, sum = 0;
for(int i = 1; i <= n; i++) {
sum += playlist[i].t;
heap.push(playlist[i].t);
if(heap.size() > k) {
sum -= heap.top();
heap.pop();
}
ans = max(ans, sum*playlist[i].b);
}
printf("%lld\n", ans);
return 0;
}