zoukankan      html  css  js  c++  java
  • BZOJ4946[Noi2017]蔬菜——线段树+堆+模拟费用流

    题目链接:

    [Noi2017]蔬菜

    题目大意:有$n$种蔬菜,每种蔬菜有$c_{i}$个,每种蔬菜每天有$x_{i}$个单位会坏掉(准确来说每天每种蔬菜坏掉的量是$x_{i}-$当天这种蔬菜卖出量),每卖出一个单位的蔬菜获得收益为$a_{i}$,第一次卖出一种蔬菜会得到$s_{i}$的额外收益,限制每天最多卖出$m$个单位的蔬菜,有$k$次询问,每次询问卖$p_{i}$天的最大收益。

    因为每种蔬菜坏掉的部分是固定的,那么我们可以将每种蔬菜分成$frac{c_{i}-1}{x_{i}}+1$类,第$i$类在第$i$天坏掉。那么对于本题就可以建出费用流模型:源点向每一天连边,容量为$m$,费用为$0$;第$i$天向第$i+1$天连边,容量为$INF$,费用为$0$;每一天向每种蔬菜这一天坏掉的那类连边,容量为这一类的数量,费用为$0$;每一类蔬菜向汇点连边,容量为这一类的数量,费用为$a_{i}$。最后在最后一天每一种蔬菜中取出一个容量新建一个点连向汇点,容量为$1$,费用为$a_{i}+s_{i}$。这样直接跑费用流会有$60pts$。我们观察一下这个图的特殊性质:只有蔬菜向汇点连的边有费用。因为每次增广一定是选取最长路,那么每次增广的路径就一定不会走有费用的反向边(即不会退流)。这也就说明对于$p=i$时选取的蔬菜一定是$p=i-1$时选取的蔬菜的父集(多选的那部分就是最便宜的那$m$个蔬菜)。那么我们可以模拟这个费用流的过程——每次选择当前能选的蔬菜中权值最大的。为了使当前的选择是可行的,设$f[i]$为在第$i$天及之前过期的蔬菜选取量,那么我们就要保证$forall i,f[i]-i*m<=0$,即$max(f[i]-i*m)<=0$,这个东西我们用线段树来维护即可。那么我们现在的问题就是如何要让当前权值最大的蔬菜尽可能的多选,因为选择第$i$天过期的蔬菜会将所有$ile j$的$f[j]$都$+1$。那么如果不能选后过期的就一定不能选先过期的,而不能选先过期的可能还能选后过期的,所以我们每次贪心地先选后过期的那部分。如果一次选择使$masx(f[i]-i*m)>0$,说明这种蔬菜剩下的都过期了,那么就撤销这次操作并在以后都不选这种蔬菜。那么我们只要用堆维护所有种类的蔬菜然后从第一天开始,每天贪心地选$m$个蔬菜并记录每天的答案在最后统一输出即可。特别注意$x_{i}=0$的蔬菜看作都在最后一天过期。

    #include<set>
    #include<map>
    #include<queue>
    #include<stack>
    #include<cmath>
    #include<cstdio>
    #include<vector>
    #include<bitset>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define ll long long
    #define pr pair<int,int>
    #define lim 100010
    using namespace std;
    int c[100010];
    int a[100010];
    int s[100010];
    int x[100010];
    int n,m,k,z;
    ll ans[100020];
    priority_queue<pr>q;
    int mx[400010];
    int sum[400010];
    ll res;
    void pushup(int rt)
    {
    	mx[rt]=max(mx[rt<<1],mx[rt<<1|1]);
    }
    void pushdown(int rt)
    {
    	if(sum[rt])
    	{
    		sum[rt<<1]+=sum[rt];
    		sum[rt<<1|1]+=sum[rt];
    		mx[rt<<1]+=sum[rt];
    		mx[rt<<1|1]+=sum[rt];
    		sum[rt]=0;
    	}
    }
    void build(int rt,int l,int r)
    {
    	if(l==r)
    	{
    		mx[rt]=-l*m;
    		return ;
    	}
    	int mid=(l+r)>>1;
    	build(rt<<1,l,mid);
    	build(rt<<1|1,mid+1,r);
    	pushup(rt);
    }
    void change(int rt,int l,int r,int L,int R,int v)
    {
    	if(L<=l&&r<=R)
    	{
    		mx[rt]+=v;
    		sum[rt]+=v;
    		return ;
    	}
    	pushdown(rt);
    	int mid=(l+r)>>1;
    	if(L<=mid)
    	{
    		change(rt<<1,l,mid,L,R,v);
    	}
    	if(R>mid)
    	{
    		change(rt<<1|1,mid+1,r,L,R,v);
    	}
    	pushup(rt);
    }
    bool judge(int id)
    {
    	int t=x[id]?(c[id]-1)/x[id]+1:lim;
    	change(1,1,lim,t,lim,1);
    	if(mx[1]<=0)
    	{
    		return 1;
    	}
    	change(1,1,lim,t,lim,-1);
    	return 0;
    }
    int main()
    {
    	scanf("%d%d%d",&n,&m,&k);
    	for(int i=1;i<=n;i++)
    	{
    		scanf("%d%d%d%d",&a[i],&s[i],&c[i],&x[i]);
    		q.push(make_pair(a[i]+s[i],i));
    	}
    	build(1,1,lim);
    	for(int i=1;i<=lim;i++)
    	{
    		int num=m;
    		while(num&&!q.empty())
    		{
    			int v=q.top().first;
    			int id=q.top().second;
    			q.pop();
    			if(!judge(id))continue;
    			res+=v,num--,c[id]--;
    			if(c[id])
    			{
    				q.push(make_pair(a[id],id));
    			}
    		}
    		ans[i]=res;
    	}
    	while(k--)
    	{
    		scanf("%d",&z);
    		printf("%lld
    ",ans[z]);
    	}
    }
  • 相关阅读:
    png 图片的缩放
    数据结构>图的最短路径
    2007年7月25日在博客园的排名上升到前400名
    C# 汉字转拼音(全拼)
    修改 Linux 主机名
    C# 事件的继承
    一个实现了 IDisposable 接口的基类
    Windows 防火墙上也有端口映射功能
    网上邻居不能访问的问题
    令网站提速的7大秘方
  • 原文地址:https://www.cnblogs.com/Khada-Jhin/p/10433272.html
Copyright © 2011-2022 走看看