题目描述:
超市里有 N 件商品,每件商品都有利润 pi 和过期时间 di,每天只能卖一件商品,过期商品不能再卖。
求合理安排每天卖的商品的情况下,可以得到的最大收益是多少。
输入格式
输入包含多组测试用例。
每组测试用例,以输入整数 N 开始,接下来输入 N 对 pi 和 di,分别代表第 i 件商品的利润和过期时间。
在输入中,数据之间可以自由穿插任意个空格或空行,输入至文件结尾时终止输入,保证数据正确。
输出格式
对于每组产品,输出一个该组的最大收益值。
每个结果占一行。
数据范围
0≤N≤10000,
1≤pi,di≤10000
最多有 14 组测试样例
输入样例:
4 50 2 10 1 20 2 30 1
7 20 1 2 1 10 3 100 2 8 2
5 20 50 10
输出样例:
80
185
分析:
本题考察贪心,由于每件物品都有一个利润值和过期时间,显然利润值相同我们优先卖过期时间早的商品;或者过期时间相同时我们优先卖利润高的物品。但是并不能像一般的贪心那样一个排序就解决了问题,比如按照过期时间从小到大排序后,第一天我们卖第一天就过期的物品中利润最高的,第二天我们卖第一天过期的剩下物品和第二天会过期的物品中利润最高的物品,但是这就是最优解方案吗?显然不是,第二天过期的物品里利润最高和次高的物品的利润都可能高于第一天过期物品中最高的利润,这时候前两天都卖第二天会过期的物品显然利润更大。这说明我们不能单纯的按照过期时间或者利润取选择每天要卖的物品,而是要动态的维护每天要卖的物品。
本题就是要在排序后动态的选择每天要卖的物品,第一天能够获得的最大利润就是过期时间不小于1的物品中利润最大的,前两天能够获得的最大的利润中就是第一天卖最大利润不会过期的物品,第二天卖过期时间不小于1的物品中利润最大的物品,同时如果过期时间不小于1的物品中利润有高于第一天卖的利润的物品,就替换掉第一天卖的物品。可见我们枚举物品的顺序依旧是按照到期时间从小到大取枚举的,并且优先选择利润高的物品。
具体的方案就是:先对所有物品按照到期时间从小到大排序,然后维护一格每天要卖出物品的集合,首先如果第一件物品的到期时间不小于1,第一天就卖第一件物品;然后枚举第二件物品,如果第二件物品的到期时间不小于2,第二天就卖第二件物品,如果第三件物品的到期时间不小于3,就第三天卖第三件物品,如果第三件物品的到期时间是2,也将该物品放进前两天要卖物品的集合里,两天卖三件物品,所以要踢出三件物品中利润最小的那个,…,所以也就是,要卖出的物品集合里面的物品个数超过了当前枚举物品的到期时间,将该物品放进去后就要从集合里踢出利润最低的物品,否则直接加入集合就行。
对于排序后的第i个物品,di为其到期时间,要么待卖物品的集合里的物品数量不超过di,就可以直接将第i个物品加进去了;如果待卖物品里物品数量超过了di,那么一定只比di多1,加入集合后删掉利润最小的物品就可以了。为什么有这个性质呢?因为加入第i-1个物品时如果是直接加入没有删除物品,说明此时集合中物品的个数一定不超过第i - 1个物品的到期时间,而第i个物品的到期时间又不会早于上一个物品的到期时间,所以加入第i件物品后集合中元素个数至多比i的到期时间多1。举个例子,如果第三件物品到期时间是3,加进去后集合里恰好有三个元素,第四件物品的到期时间如果超过3,就可以直接加入,如果等于3,那么加进去后踢掉利润最低的那个后,集合中物品个数又是3了。
我们维护的这个集合只关心集合个数,集合元素的插入以及删除最小元素,所以可以使用小根堆来维护。
代码:
#include <iostream>
#include <algorithm>
#include <sstream>
#include <queue>
using namespace std;
const int N = 10005;
pair<int,int> a[N];
int main(){
int n;
while(cin>>n) {
int p,d;
for(int i = 0;i < n;i++) {
cin>>p>>d;
a[i] = {d,p};
}
sort(a, a + n);
priority_queue<int, vector<int>, greater<int> > pq;
for(int i = 0;i < n;i++) {
d = a[i].first,p = a[i].second;
pq.push(p);
if(pq.size() > d) pq.pop();
}
int res = 0;
while(pq.size()) {
res += pq.top();
pq.pop();
}
cout<<res<<endl;
}
return 0;
}