题目链接:http://poj.org/problem?id=1456
题目大意
给定N个商品,每个商品有利润Pi和过期时间Di,每天只能卖一个商品,过期商品不能再卖,如如何安排每天卖的商品,可以使收益最大。
分析
在一定的时间内,卖的商品的利润尽可能大,肯定是最贵的先卖出,最贵的商品什么时候卖出?如果要使得利润最大化,我们可以在商品过期之间尽量晚卖出,这样就可以腾出时间卖其它较贵的商品。
本题其实是贪心算法中经典的加工生产调度问题(job sequencing with deadlines)。
详细的过程可以查看视频:https://www.youtube.com/watch?v=zPtI8q9gvX8
例
设N=7,Deadlines=4,假设所有作业都已经按收益从大小到排序。
Jobs |
Job1 |
Job2 |
Job3 |
Job4 |
Job5 |
Job6 |
Job7 |
Rrofits |
35 |
30 |
25 |
20 |
15 |
12 |
5 |
Deadlines |
3 |
4 |
4 |
2 |
3 |
1 |
2 |
有4个时间段是可使用的。
1.安排Job1,根据贪心策略,放到时间段3
Job1 |
2.安排Job2,放到时间段4
Job1 | Job2 |
3.安排Job3,Job3的deadline为4,时间段3和时间段4已经被利润更高的Job1和Job2占领,因此Job3只能放到时间段2
Job3 | Job1 | Job2 |
4.安排Job4,放到时间段1
Job4 | Job3 | Job1 | Job2 |
思考:当安排某个Job时,如何找到最近的一个未使用的时间段?
可以使用并查集来维护时间段的占用情况。
如安排完Job1时,将3和2合并为一个集合,安排Job2时,再将4和3合并为一个集合,这样在安排Job3时,可以直接定位到位置2是当前可用的。需要注意的是使用并查集维护可用的时间段时,要把位置靠前的元素设置为根。
参考代码
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
const int MAXN=10003;
struct Node{
int prof;//profit
int dl;//deadline
};
bool cmp(Node a,Node b){
return a.prof>b.prof;
}
Node a[MAXN];
int n;
int f[MAXN];
int findRoot(int x){
if(x==f[x]) return x;
else return f[x]=findRoot(f[x]);
}
void mergeEle(int x,int y){
f[y]=x; //不可写成f[x]=y,需要设置靠前的元素为根
}
int main(){
int ans=0;
while(scanf("%d",&n)!=EOF){
ans=0;
for(int i=1;i<MAXN;i++) f[i]=i;
for(int i=1;i<=n;i++)
scanf("%d%d",&a[i].prof,&a[i].dl);
sort(a+1,a+1+n,cmp);
for(int i=1;i<=n;i++){
int index=findRoot(a[i].dl);
if(index>=1) {
ans+=a[i].prof;
mergeEle(index-1,index);
}
}
printf("%d\n",ans);
}
return 0;
}