[LOJ]#2306. 「NOI2017」蔬菜 模拟费用流

本文深入探讨了在解决特定类型问题时,如何通过优化费用流算法来提高效率。首先介绍了基本的费用流实现方法,随后提出了针对无退流特性的优化策略,即使用堆和并查集维护蔬菜的选择,确保每次选择的蔬菜都是最晚过期的一批,从而避免了不必要的回流,实现了算法的高效运行。

Solution

先讲讲暴力怎么做。
显然可以费用流。设p=max{pi}p=max\{p_i\}p=max{pi},建ppp个点表示ppp天,对于一种蔬菜iii,设k=min⁡(p,⌈cixi⌉)k=\min(p,\lceil {c_i\over x_i}\rceil)k=min(p,xici),对于这种蔬菜,源点向前k−1k-1k1个点(前k−1k-1k1天)连流量为xix_ixi,费用为aia_iai的边,向最后一天的点连流量为ci−(k−1)xi−1c_i-(k-1)x_i-1ci(k1)xi1,费用为aia_iai的边和流量为111,费用为ai+sia_i+s_iai+si的边,然后对于每一天的点,向前一天的点连流量为infinfinf,费用为000的边,再向汇点连流量为mmm,费用为000的边。这样可以保证每天的可用蔬菜都是合法的。因为是多组询问,可以这样:询问从小到大排序,每次增加一些流量继续跑,在LOJ上获得68分的好成绩。
考虑优化。注意到第iii天选择的蔬菜肯定是第i+1i+1i+1天选择蔬菜的子集,所以这个费用流实际上是没有退流的,所以可以模拟费用流。考虑用一个堆维护当前可用的蔬菜,那么当前的这种蔬菜肯定是选择最晚过期的那一批流到对应的那天,感性地理解一下,能够流到前面几天的更多,所以肯定是流向尽量后,用一个堆+并查集维护就行了。

Code

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define pa pair<int,int>
const int Maxn=100010,N=100000;
const int inf=2147483647;
int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x*f;
}
int n,m,k,f[Maxn],cnt[Maxn];LL ans=0,Ans[Maxn];
int ff(int x){return((f[x]==x)?x:f[x]=ff(f[x]));}
struct P{int a,s,c,x;}p[Maxn];
struct Q{int x,id;}q[Maxn];
bool cmp(Q a,Q b){return a.x<b.x;}
struct Node
{
	int v,id;
	Node(int _v,int _id){v=_v,id=_id;}
};
bool operator<(Node a,Node b){return a.v<b.v;}
priority_queue<Node>que;
int main()
{
	n=read(),m=read(),k=read();
	for(int i=1;i<=n;i++)
	{
		p[i].a=read(),p[i].s=read(),p[i].c=read(),p[i].x=read();
		que.push(Node(p[i].a+p[i].s,i));
	}
	for(int i=0;i<=N;i++)f[i]=i,cnt[i]=0;
	for(int i=1;i<=N;i++)
	{
		int tmp=m;
		while(tmp&&!que.empty())
		{
			Node t=que.top();que.pop();
			int pos;
			if(!p[t.id].x)pos=N;else pos=min(p[t.id].c/p[t.id].x+(p[t.id].c%p[t.id].x!=0),N);
			int Pos=ff(pos);if(!Pos)continue;
			cnt[Pos]++;if(cnt[Pos]==m)f[Pos]=ff(Pos-1);
			ans+=t.v;p[t.id].c--;if(p[t.id].c)que.push(Node(p[t.id].a,t.id));
			tmp--;
		}
		Ans[i]=ans;
	}
	while(k--)printf("%lld\n",Ans[read()]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值