【贪心+堆/模拟费用流增广】BZOJ4946 [NOI2017]蔬菜

版权声明:这是蒟蒻的BLOG,神犇转载也要吱一声哦~ https://blog.csdn.net/Dream_Lolita/article/details/84586955

一道思路很好的题,因为篇幅太长赶时间,以下多数转自这里

【题目】
定义了一种蔬菜为: a i , s i , c i , x i a_i,s_i,c_i,x_i ,有 n n 种蔬菜
意思是蔬菜的价格为 a i a_i ,第一份卖出时价格为 a i + s i a_i+s_i ,一共有 c i c_i 份,每天会有 x i x_i 份过期;每天最多卖出 m m 份蔬菜,多组输入天数 d d 依次最大化收入。
n , m , d , T 1 0 5 n,m,d,T\leq 10^5

【解题思路】
首先有一个费用流的做法是这样子的:首先对于蔬菜拆点,每一天拆出一个点,因为蔬菜可以购买的量逐渐递减,因此每一天向下一天连接流量为当前天减少 d d 的边,费用为 0 0 ,然后考虑把蔬菜卖出去,那么就是从一个蔬菜拆出来的某一天向汇点连边,因为限制每天购买的总量,所以再对于每一天的购买的蔬菜拆一个点,然后这一天的每一个蔬菜向这个点连容量为 i n f inf ,费用为蔬菜费用的边,再从这个点向汇点连容量为 m m ,费用 0 为0 的边。显然源点向每个蔬菜的第一天连容量为蔬菜数量,费用为 0 0 的边,至于第一次购买产生的额外贡献,我们把连的那条边拆出一个单位来,再额外链接一下容量为 1 1 ,费用为第一次购买产生的额外贡献的边。这样子连边就好了。

跳出来,往正解的方面想。
显然不难发现一个 O ( n Q ) O(nQ) 的贪心,蔬菜会逐渐减少很不好做,我们倒过来,反过来考虑每一天,那么蔬菜的数量变成了每一天都增加每种蔬菜一定量,然后我们需要倒着买蔬菜就好了。可能需要数据结构什么的维护一下,但是大致的复杂度就是上述的东西。

我们现在再正着考虑,假设我们知道我们在 p p 天的时候的最优解中,买了哪些蔬菜,那么我们可以很容易的得到 p 1 p-1 天的答案,显然只需要把利润最小的那 m m 个蔬菜给去掉就好了,因为第 p 1 p-1 天可以购买的蔬菜不会少于第 p p 天,在第 p p 天能够买到的,在 p 1 p-1 天也一定能够买到。

前面说的不是很清楚,现在考虑如何求解第 p p 天的答案。我们既然是增加蔬菜,那么这个操作很容易维护,只需要搞一个堆出来,然后每次把当前所拥有的所有蔬菜全部拿出来贪心取就好了,稍微注意一下第一次选产生的额外贡献的细节就好。然后考虑如何递推回去,还是拿一个堆维护,同理注意一下第一次选产生的额外贡献。

既然这么讲了贪心怎么写,是不是觉得其实这就是一个模拟费用流的过程啊,倒推回去就是一个退流的过程,正推的贪心,显然每天只有那么几条路径,用堆维护等价于跑费用流。

复杂度 O ( n l o g n + q m ( l o g   n + l o g   q ) ) O(nlogn +qm(log\ n+log\ q))

【参考代码】

#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
using namespace std;

typedef long long ll;
typedef pair<int,int> pii;
const int N=1e5+10,P=1e5;
int n,m,Q,sum;
int used[N],vis[N];
ll ans[N];
vector<int>d[N];
stack<pii>s;
priority_queue<pii>q;

int read()
{
	int ret=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
	return ret;
}

struct data
{
	int a,s,c,x;
	void rd(){a=read();s=read();c=read();x=read();}
}a[N];

int main()
{
#ifndef ONLINE_JUDGE
	freopen("BZOJ4946.in","r",stdin);
	freopen("BZOJ4946.out","w",stdout);
#endif
	n=read();m=read();Q=read();
	for(int i=1;i<=n;++i) a[i].rd();
	for(int i=1;i<=n;++i) 
		if(!a[i].x) d[P].pb(i); else d[min(P,(a[i].c+a[i].x-1)/a[i].x)].pb(i);
	for(int i=P;i;--i)
	{
		for(int j=0,id;j<(int)d[i].size();++j) 
			id=d[i][j],q.push(mkp(a[id].a+a[id].s,id));
		for(int j=m;j && !q.empty();)
		{
			int w=q.top().fi,v=q.top().se;q.pop();
			if(!vis[v])
			{
				vis[v]=1;ans[P]+=w;++used[v];--j;
				if(a[v].c>1) q.push(mkp(a[v].a,v));
			}
			else
			{
				int res=min(j,a[v].c-used[v]-(i-1)*a[v].x);
				ans[P]+=(ll)res*w;used[v]+=res;j-=res;
				if(used[v]^a[v].c) s.push(mkp(a[v].a,v));
			}
		}
		while(!s.empty()) q.push(s.top()),s.pop();
	}
	while(!q.empty()) q.pop();
	for(int i=1;i<=n;++i) sum+=used[i];
	for(int i=1;i<=n;++i) 
		if(used[i]==1) q.push(mkp(-a[i].s-a[i].a,i));
		else if(used[i]) q.push(mkp(-a[i].a,i));
	for(int i=P-1;i;--i)
	{
		ans[i]=ans[i+1];
		while(sum>i*m && !q.empty())
		{
			int w=-q.top().fi,v=q.top().se;q.pop();
			if(used[v]>1)
			{
				int res=min(sum-i*m,used[v]-1);
				used[v]-=res;sum-=res;ans[i]-=(ll)res*w;
				if(used[v]==1) q.push(mkp(-a[v].a-a[v].s,v));
				else q.push(mkp(-a[v].a,v));
			}
			else --sum,--used[v],ans[i]-=w;
		}
	}
	while(Q--) printf("%lld\n",ans[read()]);

	return 0;
}

【总结】
niubi

猜你喜欢

转载自blog.csdn.net/Dream_Lolita/article/details/84586955