BZOJ 3874: [Ahoi2014]宅男计划

题意:外卖店一共有N种食物,分别有1到N编号。第i种食物有固定的价钱Pi和保质期Si。第i种食物会在Si天后过期。JYY是不会吃过期食物的。比如JYY如果今天点了一份保质期为1天的食物,那么JYY必须在今天或者明天把这个食物吃掉,否则这个食物就再也不能吃了。保质期可以为0天,这样这份食物就必须在购买当天吃掉。JYY现在有M块钱,每一次叫外卖需要额外付给送外卖小哥外送费F元。送外卖的小哥身强力壮,可以瞬间给JYY带来任意多份食物。JYY想知道,在满足每天都能吃到至少一顿没过期的外卖的情况下,他可以最多宅多少天呢?

看了WNJXYK的题解,感觉太强了。这题如果DP可以做到60分,但M达到10^18就处理不了了。首先有一个贪心,对于食物i,j,如果si>=sj,pi<=pj,那么j显然无用,删去。接下来按照保质期排序,根据我们前面的操作,现在一定是保质期越小价格越低,所以从保质期小的开始取,能多买则多买,这样一定是最优的。但现在还有一个问题,可以买多次,但题目买的次数和生存天数之间有二次函数的关系(别问我,我也不知道怎么证),所以三分买的次数,计算答案。
Tips:1、因为可以买多次,贪心的时候要注意可能有食物在一些次数中可以买,另一些不能买(花费除以次数*价格可能有剩余),但这种情况只会出现在最后一种购买的食物中,所以要特别判断一下。
2、到处开long long(我查了好久)

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn=200+10;
long long m,f;
int n,del[maxn],tot;
struct node
{
  long long p,s;
}a[maxn],hm[maxn];
int cmp(node a,node b)
{
  if(a.p!=b.p) return a.p<b.p;
  else return a.s<b.s;
}
int cmp1(node a,node b)
{
  return a.s<b.s;
}
long long calc(long long k)
{
  long long cost=m-f*k,day=0,res=0;
  for(int i=1;i<=n;i++) //尽量买保质期小的
  {
    if(a[i].s>=day)
    {
      long long num=min((cost/a[i].p)/k,a[i].s-day+1);
      day+=num;res+=num*(long long)k;
      cost-=num*a[i].p*k;
    }
    if(a[i].s>=day)
    {
      long long v=min(k,cost/a[i].p);
      day++;res+=v;
      cost-=a[i].p*v;
    }
  }
  return res;
}
int main()
{
  //freopen("food.in","r",stdin);
  //freopen("food.out","w",stdout);
  scanf("%lld%lld%d",&m,&f,&n);
  for(int i=1;i<=n;i++)
    scanf("%lld%lld",&hm[i].p,&hm[i].s);
  long long mini=hm[1].p;
  sort(hm+1,hm+n+1,cmp);
  for(int i=1;i<=n;i++)
  {
    int now=i;mini=min(mini,hm[i].p);
    while(i+1<=n&&hm[i+1].s<=hm[now].s)
    {
      del[i+1]=1;
      i++;
    }
  }
  for(int i=1;i<=n;i++)
    if(!del[i])
    {
      a[++tot].p=hm[i].p;a[tot].s=hm[i].s;
    }
  n=tot;
  sort(a+1,a+n+1,cmp1);
  long long l=1,r=m/(f+mini);
  while(l+25<r)
  {
    long long x1=l+(r-l)/3;
    long long x2=l+(r-l)*(long long)2/(long long)3;
    if(calc(x1)<calc(x2)) l=x1;
    else r=x2;
  }
  long long res=calc(l);
  for(long long i=l+1;i<=r;i++)
     res=max(calc(i),res);
  printf("%lld\n",res);
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值