给定N个商品,每个商品有利润 pi 和过期时间 di,每天只能卖一个商品,过期商品不能再卖,求如何安排每天卖的商品,可以使收益最大。
先对所有的商品按照时间进行一下排序,再依次将商品加入优先队列,加入优先队列的时候需要进行一下判断:
1.当前商品的日期 == 优先队列中的商品数量 && 当前商品的价值大于小根堆堆顶元素的价值
那么将堆顶元素弹出,并将当前商品加入优先队列
2.当前商品的日期 != 优先队列中的商品数量
将当前商品加入优先队列
本题就是一个贪心的做法,对于与时间排序发生冲突的商品,将冲突商品和之前商品的最小值进行比较,如果冲突商品权值更小,则将商品最小值弹出队列,最后队列中剩下的元素都是符合条件,并且最优的。
#include
#include
#include
#include
#include
#define rep(i,a,b) for(int i = a;i <= b;i++)
using namespace std;
typedef long long ll;
const int N = 1e4+100;
int n;
priority_queue > q;
pair goods[N];
bool cmp(pair a, pair b)
{
return a.second < b.second;
}
int main()
{
while(~scanf("%d",&n))
{
while(q.size()) q.pop();
rep(i,1,n)
{
int x,y;
scanf("%d%d",&x,&y);
goods[i] = make_pair(-x,y);
}
sort(goods+1,goods+1+n,cmp);
rep(i,1,n)
{
if(goods[i].second == q.size() && goods[i].first < q.top().first)
{
q.pop();
q.push(goods[i]);
}
else if(goods[i].second != q.size()) q.push(goods[i]);
}
ll ans = 0;
while(q.size())
{
ans += q.top().first;
q.pop();
}
printf("%lld\n",-ans);
}
return 0;
}
可以发现这道题每一个商品只会占用一个日期,因此对于商品的价值进行排序,只要这个商品可以选,那么就选这个商品。
那么本题就变成了一个简单的贪心算法,那和并查集有什么关系呢?
我们继续看这个贪心算法,排序简单,但是如何判断这个商品可不可以选呢,就是如何判断这个商品过期之前的天数有没有空位呢?之前我们根据优先队列的大小进行了一次判断,现在我们通过并查集也可以实现这个判断。
具体方法是,我们先给所有的日期建立一个数组,一开始每个日期都指向自己。
每当读入一个商品时,我们去判断这个商品的日期是否指向0,如果指向0,代表这个商品之前的日期都已经被占满了,因此不可填。
如果不是0,则代表这个商品可以填,那么我们就选取了这个商品,选取完这个商品之后,我们需要将这个 day = get( i ),day就是当前日期所指向的日期。
将这个日期指向 fa[ i ] = get(day - 1),表示这个商品被选取了。
#include
#include
#include
#include
#define rep(i,a,b) for(int i = a;i <= b;i++)
using namespace std;
const int N = 10010;
typedef long long ll;
int fa[N],n;
pair goods[N];
int get(int x)
{
if(fa[x] == x) return x;
else return fa[x] = get(fa[x]);
}
int main()
{
while(~scanf("%d",&n))
{
int m = -1;
rep(i,1,n)
{
int x,y;
scanf("%d%d",&x,&y);
goods[i] = make_pair(-x,y);
m = max(m,y);
}
sort(goods+1,goods+1+n);
ll ans = 0;
fa[0] = 0;
rep(i,1,m) fa[i] = i;
rep(i,1,n)
{
int day = get(goods[i].second);
if(day == 0) continue;
ans += goods[i].first;
fa[day] = get(day-1);
}
printf("%lld\n",-ans);
}
return 0;
}