1. 程式人生 > >BZOJ4946[Noi2017]蔬菜——線段樹+堆+模擬費用流

BZOJ4946[Noi2017]蔬菜——線段樹+堆+模擬費用流

can 固定 span make 鏈接 lap 新建 main 額外

題目鏈接:

[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$天過期的蔬菜會將所有$i\le 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\n",ans[z]);
	}
}

BZOJ4946[Noi2017]蔬菜——線段樹+堆+模擬費用流