贪心(难)

P3826 [NOI2017]蔬菜

题意:有n中蔬菜,第i中蔬菜单价为 a i a_i ai,每次卖出一种以前没卖出过的蔬菜时,会有 s i s_i si的额外收入,一开始的每种蔬菜的库存为 c i c_i ci个单位,每天会坏掉 x i x_i xi个单位的蔬菜。每天最多能卖出m个单位的蔬菜,给出k个询问,问第P天最多能够获得的价值

正常思路:先不考虑额外的收入,我们每天肯定是选择最贵的菜卖,每次当有一种蔬菜要消失的时候,去考虑一下卖出这种蔬菜,获得的额外价值。但是有k个询问,并不好处理。
思路:逆向考虑,从最后一天往前推,那么每往前一天都会有新的蔬菜,每一天都是选择最贵的菜卖出去
1、首先枚举蔬菜,看蔬菜最后一天出现在哪, x i x_i xi为0的蔬菜认为存在在任意一天
2、然后从后往前枚举第几天,把这一天的蔬菜放入堆中,取最贵的蔬菜卖出。卖出的时候要区分是否为第一次卖出,第一次卖出价格为 a [ i ] + s [ i ] a[i]+s[i] a[i]+s[i],否则为 a [ i ] a[i] a[i]
3、但是有k次询问,所以我们从最后一天往前递推答案,前一天比后一天少卖出m个蔬菜,那么我们就从后一天中选择价值最低的m个蔬菜弹出,在弹出蔬菜的时候,如果该蔬菜已经卖出的数量大于1,那么分两段来弹出,至少保留一个,然后再把剩余的蔬菜放入堆中

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <queue>
#include <vector>
#include <set>
#include <map>
#include <cstring>
#include <string>
#include <cmath>
#define rep(i,a,b) for (int i=a; i<=b; ++i)
#define per(i,b,a) for (int i=b; i>=a; --i)
#define mes(a,b)  memset(a,b,sizeof(a))
#define mp make_pair
#define ll long long
#define pb push_back
#define ls (rt<<1)
#define rs ((rt<<1)|1)
#define isZero(d)  (abs(d) < 1e-8)
using namespace std;
const int maxn=1e5+5,INF=0x3f3f3f3f;
const int mod=1e9+7;

vector<int> date[maxn];
bool used[maxn];
ll  sold[maxn],a[maxn],s[maxn],c[maxn],x[maxn],ans[maxn];
int n,m,k,p=1e5;
queue<int> reuse;

struct Vegetable
{
	ll price;
	int no;
	bool operator<(const Vegetable& b) const
	{
		return price<b.price;
	}
};

struct cmp
{
	bool operator()(const Vegetable&a,const Vegetable&b) const
	{
		return a.price>b.price;
	}
};

priority_queue<Vegetable> pq;
priority_queue<Vegetable,vector<Vegetable>,cmp> pq2;

int main()
{
	scanf("%d%d%d",&n,&m,&k);
	rep(i,1,n)
		scanf("%d%d%d%d",&a[i],&s[i],&c[i],&x[i]);
	rep(i,1,n)
	{
		if(x[i]==0)
			date[p].pb(i);
		else
			date[min(1ll*p,(c[i]+x[i]-1)/x[i])].pb(i);
	}
	for(int i=p;i>=1;--i)
	{
		for(int j=0;j<date[i].size();++j)
			pq.push({a[date[i][j]]+s[date[i][j]],date[i][j]});
		if(pq.empty())
			continue;
		ll goods=m;
		while(goods&&!pq.empty())
		{
			auto it=pq.top();
			if(used[it.no]==false)
			{
				used[it.no]=true;
				ans[p]+=it.price;
				sold[it.no]++;
				goods--;
				if(c[it.no]!=1)
					pq.push({a[it.no],it.no});
			}
			else
			{
				//每次往前推,i会变小,也就是有新的蔬菜到达
				ll remain=c[it.no]-sold[it.no]-(i-1)*x[it.no];
				ll sell=min(remain,goods);
				ans[p]+=sell*it.price;
				sold[it.no]+=sell;
				goods-=sell;
				//如果已卖出的数量还不到库存数量,那么继续放入堆中
				if(sold[it.no]!=c[it.no])
					reuse.push(it.no);
			}
			pq.pop();
		}
		while(!reuse.empty())
		{
			auto it=reuse.front();
			pq.push({a[it],it});
			reuse.pop();
		}
	}
	ll total=0;
	//区分卖出一个还是多个
	for(int i=1;i<=n;++i)
	{
		if(sold[i]==1)
			pq2.push({a[i]+s[i],i});
		else if(sold[i]!=0)
			pq2.push({a[i],i});
		total+=sold[i];
	}
	for(int i=p-1;i>=1;--i)
	{
		ans[i]=ans[i+1];
		if(total<=m*i)
			continue;
		ll goods=total-m*i;
		while(goods&&!pq2.empty())
		{
			auto it=pq2.top();
			pq2.pop();
			if(sold[it.no]!=1)
			{
				ll sell=min(sold[it.no]-1,goods);
				sold[it.no]-=sell;
				goods-=sell;
				ans[i]-=sell*it.price;
				if(sold[it.no]==1)
					pq2.push({a[it.no]+s[it.no],it.no});
				else
					pq2.push({a[it.no],it.no});
			}
			else
			{
				goods--;
				sold[it.no]--;
				ans[i]-=it.price;
			}
		}
		total=m*i;
	}
	int x;
	for(int i=1;i<=k;++i)
	{
		scanf("%d",&x);
		printf("%lld\n",ans[x]);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值