【贪心】 POJ 1456 Supermarket 详解 (并查集/二叉堆优化)

题目链接:POJ-1456

题目大意


有n件商品,每件商品的利润为p_i,销售日期的截止时间为d_i(即只能在d_i时间前销售该物品)。一天只能销售一件物品。问这n件商品的最大利润为多少

方案一

分析

有两种贪心策略,现介绍第一种:

将商品按利润从大到小排序,优先考虑利润大的商品。那么该商品可以在其截止日期前的任一时间销售,那么肯定越晚销售越好,因为越晚销售对其他商品的影响越小,越有可能卖出更多的截止时间在它之前的商品。

核心代码:

p数组储存商品,sold数组记录某一天是否已经被占用了。

假设一件商品的截至时间为d,因为我们希望它能尽可能晚地卖出去,所以从第d天开始不断往前找空闲的时间点,找到之后将该时间点标记为被占用

 for(int i=1;i<=N;i++)
          for(int j=p[i].deadline;j>=1;j--){
              if(!sold[j]){
                  sold[j]=1;
                  ans+=p[i].profit;
                  break;
              }
          }

复杂度:O(N^2)

但是这道题数据比较弱仍然可以过

优化

朴素的做法在枚举每件商品时都会从其截至日期不断往前找没被占用的时间点

我们可以用一个pre数组记录该时间点前最近的一个空闲时间点

比如已知第4-17天都被占用了,而一件商品的截至时间是第16天

那么pre[16]=3,即指向下一个空闲的时间点,我们就将该商品在第3天卖掉即可,而不用从16再枚举到3了。

如果pre[x]=0,则表明1-x天都被占用了,说明该商品不可能卖出去了

这么做就可以避免暴力找解

具体实现:

首先初始化pre数组为-1,表示都空闲

当第x天被占用,我们将pre[x]的值修改为x-1,表示下一个可能空闲的时间点是第x-1天

之所以是可能空闲的时间点,因为可能第x-1天也被占用了

可以联想树结构方便理解:

每一个结点的父结点都是下个可能空闲时间的结点,而根节点就是空闲的时间点

我们用一个_find函数来从结点遍历到根节点就能找到空闲的时间点了

而其实我们希望pre数组能直接指向根节点

因此在查找函数中路径压缩即可

其实这就是一个并查集了,要不断查询某一个时间点的前一个可能空闲时间点,还需要合并操作(如果某时间点x被占用,则合并到时间点x-1的集合中去,成为x-1结点的子树)

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;

typedef struct{
  int profit;
  int deadline;
}product;

product p[10005];
int pre[100005];

bool cmp(product a,product b){
   if(a.profit!=b.profit)
       return a.profit>b.profit;
   return a.deadline>b.deadline;
}

int _find(int x){
   return pre[x]==-1?x:pre[x]=_find(pre[x]);
}

int main()
{
    int N;
    while(~scanf("%d",&N)){
       memset(pre,-1,sizeof(pre));
       for(int i=1;i<=N;i++)
         scanf("%d %d",&p[i].profit,&p[i].deadline);

       sort(p+1,p+N+1,cmp);
       int ans=0;
       for(int i=1;i<=N;i++){
           int time=_find(p[i].deadline);
           if(time>0){//如果为0则说明这个商品已经没有位置可以放了
              ans+=p[i].profit;
              pre[time]=time-1;
           }
       }

       printf("%d\n",ans);
    }
    return 0;
}

 

方案二

分析

还有另外一种贪心策略,按商品的截止时间从小到大排,优先考虑截止时间前的商品。当发现当前商品截止时间为d,利润为p且无法售卖时(即前d天已经安排了d种商品售卖),寻找这d种商品利润最小的,如果该利润小于p,则可以用当前商品去替换掉利润最小的商品,同样是O(N^2)的做法,但是可以利用小根堆进行优化。

代码

#include <cstdio>
#include <string.h>
#include <iostream>
#include <map>
#include <algorithm>
using namespace std;

const int maxn=1e4+5;
typedef struct{
   int profit;
   int deadline;
}product;

bool operator < (product a,product b){
  return a.deadline<b.deadline;
}

product goods[maxn];

int heap[maxn]; //小根堆
int cnt=1;

void up(int p){//向上调整
  while(p>1){
     if(heap[p]<heap[p/2]){
        swap(heap[p],heap[p/2]);
        p/=2;
     }
     else break;
  }
}

void down(int p){ //向下调整
   int s=p*2;
   while(s<cnt){
      if(s<cnt-1&&heap[s]>heap[s+1]) s++;
      if(heap[s]<heap[p]){
          swap(heap[s],heap[p]);
          p=s;
          s=p*2;
      }
      else break;
   }
}

void Insert(int value){
    heap[cnt]=value;
    up(cnt);
    cnt++;
}

void Extract(){
    heap[1]=heap[--cnt];
    down(1);
}

void Remove(int k){
    heap[k]=heap[--cnt];
    up(k); down(k);
}


void init(){
  memset(heap,0,sizeof(heap));
  cnt=1;
}

int main()
{
  int N;
  while(~scanf("%d",&N)){
     init();
     for(int i=1;i<=N;i++)
       scanf("%d %d",&goods[i].profit,&goods[i].deadline);
     sort(goods+1,goods+N+1);
     int num=0;
     for(int i=1;i<=N;i++){
         if(goods[i].deadline>num){
             Insert(goods[i].profit);
             num++;
         }
         else{
             if(goods[i].profit>heap[1]){
                 Extract();
                 Insert(goods[i].profit);
             }
         }
     }
     int ans=0;
     for(int i=1;i<=num;i++)
         ans+=heap[i];
     printf("%d\n",ans);
  }
	return 0;
}

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值