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

Solution

先讲讲暴力怎么做。
显然可以费用流。设 p = m a x { p i } p=max\{p_i\} p=max{pi},建 p p p个点表示 p p p天,对于一种蔬菜 i i i,设 k = min ⁡ ( p , ⌈ c i x i ⌉ ) k=\min(p,\lceil {c_i\over x_i}\rceil) k=min(p,xici),对于这种蔬菜,源点向前 k − 1 k-1 k1个点(前 k − 1 k-1 k1天)连流量为 x i x_i xi,费用为 a i a_i ai的边,向最后一天的点连流量为 c i − ( k − 1 ) x i − 1 c_i-(k-1)x_i-1 ci(k1)xi1,费用为 a i a_i ai的边和流量为 1 1 1,费用为 a i + s i a_i+s_i ai+si的边,然后对于每一天的点,向前一天的点连流量为 i n f inf inf,费用为 0 0 0的边,再向汇点连流量为 m m m,费用为 0 0 0的边。这样可以保证每天的可用蔬菜都是合法的。因为是多组询问,可以这样:询问从小到大排序,每次增加一些流量继续跑,在LOJ上获得68分的好成绩。
考虑优化。注意到第 i i i天选择的蔬菜肯定是第 i + 1 i+1 i+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()]);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值