一.贪心常见题型
在提高组难度以下的题目中,最常见的贪心有两种。
- 「我们将 XXX 按照某某顺序排序,然后按某种顺序(例如从小到大)选择。」。
- 「我们每次都取 XXX 中最大/小的东西,并更新 XXX。」(有时「XXX 中最大/小的东西」可以优化,比如用优先队列维护)
二者的区别在于一种是离线的,先处理后选择;一种是在线的,边处理边选择。
而本题采用是将既要排序,又要用小根堆维护准备售卖商品。
二.问题分析
问题:卖尽可能多的商品,但要保证商品不过期。
方法1:排序 + 小根堆
小根堆:维护准备售卖商品,堆顶为利润最小的商品。
之所以是准备售卖的商品,是因为在小根堆里的商品有可能会被替换出去。
按照过期时间排序,
枚举,假设每一个商品都加入堆中。
然而我们知道在有限的时间内不可能售完所有商品而让每一个商品都不过期。
所以这里要做出判断,
当枚举到第i个商品时,
1.如果商品的过期天数 T[i] > 堆中商品的数量num
说明,假设我们在 第T[i]天 卖掉第 i 个商品,则可以保证第 i 个商品不过期。
那么在第 T[i] 天前,我们显然是有足够的时间卖掉num个商品。
而又由于每个商品在入堆时都是保证其不过期的,所以我们可以说此过程完全合法。
2.如果商品的过期天数 T[i] == 堆中商品的数量num
说明,假设我们在 第T[i]天 卖掉第 i 个商品,则可以保证第 i 个 商品不过期。
但是在第 T[i] 天前,我们显然是没有足够的时间卖掉num个商品,而只能卖掉 num - 1 个商品。
那么这意味着我们至少需要牺牲一个商品。
而我们目前已知,前 T[i] 天 要卖掉 num + 1 个商品,而我们最大只能卖掉num个商品(T[i] == num)。
而我们知道num <= T[i - 1] , 而此时num == T[i], 且 T[i - 1] <= T[i]。
那么只能说明,第 i - 1 个 商品 的 T[i - 1] 和 第 i 个 商品 的 T[i] 有: T[i - 1] == T[i]。
那么,按照贪心原则,在T[i - 1] == T[i] 的情况下,我们应该保证牺牲 商品 i 和 商品 i - 1 中利润更小的一个。
假设被牺牲的是商品i - 1。
但是,我们知道,商品i - 1 并不一定是 num 个商品中利润最小的一个, 而它完全可以在卖掉利润最小的商品的那天,不卖掉利润最小的商品,转而去卖掉第 i - 1 个商品。
而由此我们看出:被牺牲的是第i个商品 和 堆中商品 中 利润最小的一个。(代码实现时就是对比第 i 个商品 和 堆顶 的利润)
3.如果商品的过期天数 T[i] < 堆中商品的数量num。
而我们知道num <= T[i - 1]。而T[i - 1] <= T[i](我们自己做的排序)。所以这种情况不会出现。
三.实现细节(见代码)
#include <bits/stdc++.h>
using namespace std;
#define endl "\n"
#define x first
#define y second
typedef pair<int,int> PII;
const int N = 3e5 + 10;
int n;
pair<int, int> a[N];
priority_queue<int, vector<int>, greater<int>> heap;
void solve()
{
while(cin >> n)
{
for(int i = 1; i <= n; i ++)
cin >> a[i].y >> a[i].x;
sort(a + 1, a + 1 + n);
for(int i = 1; i <= n; i ++)
{
if(a[i].x == heap.size() && heap.top() < a[i].y)
{
heap.pop(); heap.push(a[i].y);
continue;
}
else if(a[i].x > heap.size())
heap.push(a[i].y);
}
int ans = 0;
while(heap.size())
{
ans += heap.top();
heap.pop();
}
cout << ans << endl;
}
}
int main(){
solve();}