1010 - 网络流之最大费用流 - Candy(HDU 4322)

传送门​​​​​​

 

题意

现在有  n  颗糖果,要将其分给 m 个小朋友,每个小朋友都有特定的喜好,如果他得到了自己喜欢的糖果,那么他将增加K的欢乐值,否则就只会增加1的欢乐值。当第 i 个小朋友的欢乐值大于等于Bi时,他才是高兴的

问是否存在一种分配方案,使得所有小朋友都高兴

 

分析

这这这,要是不讲,我是完全看不出来网络流的啊……怎么那么菜……

首先,因为要使所有小朋友都高兴,那我们肯定尽量的给每一个小朋友他喜欢的糖果,现在就来考虑这样的特殊糖果

建立一个源点

从源点往每一个糖果连一条流量为1,费用为0的边(流量限制使用次数,费用在这儿暂时没用)

每一个糖果往喜欢他的小朋友那里连边,连流量为1,费用为0 的边

再从每一个小朋友往汇点连边

然后难点就来了,这往汇点连的边该如何限制才能保证一条流跑下来是合法的呢?

边:容量是 b[i] / k ,费用是 k ,

因为我们知道一个小朋友的欢乐值要大于等于Bi,就先尽量用这个小朋友喜欢的糖果去搞。由于这里的建图只涉及了特殊糖果,那么这些边的流量限制就是  b[i]/k ,说明对于第i个小朋友只能选b[i]/k个特殊糖果。因为如果还要选的话就会造成浪费,我们当然不希望这样的情况发生

但如果b[i]%k!=0,说明光用特殊糖果不能刚好填完这个小朋友的欢乐值,我们就还需要一些糖果

再分细一点:

当b[i] % k == 1时,此后再选的话,特殊糖和普通的糖无异,没必要纳入考虑。

如果>1,这时候就需要再建第二条边(j,t,1,b[i]%k),容量是1,费用是b[i]%k,它的意义就是用一个喜欢的糖果把b[i]%k的部分填补掉。

注意这样的正确性:孩子和汇点间的第二条边连接时,费用是b[i]%k,因为如果是k的话,在最后判断时相当于“会将多出来的那部分欢乐值”分给别的孩子。但实际上多出来那部分是浪费的,所以正确的是添加费用为b[i]%k的边。

但这时候再考虑一个问题,就是如果你套最小费用最大流的板子,它肯定会先走第二条边,但这样是不对的。这样的话就有可能导致费用是k的边的容量有剩余时,而第二条边已经被流了,道理自己想吧。所以我们需要做的就是先让它流费用大的,所以这是个最大费用最大流。只需要把费用取相反数,流完再取反就好了。

最后判断n-ans>=all(b[i])-cost是否成立就好了,也就是剩余的没人喜欢(或者被某些孩子喜欢但这个孩子快乐度够了而不能要)的糖果是不是能满足剩余的快乐度。这个讲得好

 

代码

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<queue>
#define in read()
#define M 4000000
#define inf 10000000
#define N 500
using namespace std;
inline int read(){
	char ch;int f=1,res=0;
	while((ch=getchar())<'0'||ch>'9') if(ch=='-') f=-1;
	while(ch>='0'&&ch<='9'){
		res=(res<<3)+(res<<1)+ch-'0';
		ch=getchar();
	}
	return f==1?res:-res;
}
int n,f,d,S=0,T;
int nxt[M],to[M],cap[M],head[N],cur[N],cnt=1,lev[N],w[M];
void add(int x,int y,int z,int ww){
	nxt[++cnt]=head[x];head[x]=cnt;to[cnt]=y;cap[cnt]=z;w[cnt]=ww;
	nxt[++cnt]=head[y];head[y]=cnt;to[cnt]=x;cap[cnt]=0;w[cnt]=-ww;
}
int dis[N],walk[N],vis[N];
bool spfa(){
	for(int i=S;i<=T;++i){
		cur[i]=head[i];dis[i]=inf;
		walk[i]=0;vis[i]=0;
	} 
	queue<int > q;
	q.push(S);vis[S]=1;dis[S]=0;
	while(!q.empty()){
		int u=q.front();q.pop();vis[u]=0;
		for(int e=head[u];e;e=nxt[e]){
			int v=to[e];
			if(dis[u]+w[e]<dis[v]&&cap[e]>0){
				dis[v]=dis[u]+w[e];
				if(!vis[v]){
					q.push(v);
					vis[v]=1;
				}	
			}
		}
	}
	if(dis[T]!=inf) return true;
	return false;
}
int cost=0;
int dinic(int u,int flow){
	if(u==T){
		cost+=flow*dis[T];
		return flow;
	} 
	int delta,res=0;
	walk[u]=1;
	for(int &e=cur[u];e;e=nxt[e]){
		int v=to[e];
		if(dis[v]==dis[u]+w[e]&&cap[e]>0&&!walk[v]){
			delta=dinic(v,min(cap[e],flow-res));
			if(delta){
				res+=delta;cap[e]-=delta;
				cap[e^1]+=delta;if(res==flow) return flow;
			}
		}
	}
	return res;
}
int t,m,k;
int b[20];
int main(){
	t=in;int tt=0;
	while(t--){
		tt++;cost=0;
		memset(head,0,sizeof(head));
		n=in;m=in;k=in;
		T=n+m+1;S=0;
		int i,j,tot=0;
		for(i=1;i<=n;++i) add(S,i,1,0);
		for(i=1;i<=m;++i) b[i]=in,tot+=b[i];
		for(i=1;i<=m;++i)
			for(j=1;j<=n;++j)
			{
				int p=in;
				if(p==1) add(j,i+n,1,0);
			}
		for(i=1;i<=m;++i){
			int ii=i+n;
			add(ii,T,b[i]/k,-k);
			if(b[i]%k>1)	add(ii,T,1,-b[i]%k);
		}
		int maxflow=0;
		while(spfa()) maxflow+=dinic(S,inf);
		cost=-cost;
		if(n-maxflow>=tot-cost) printf("Case #%d: YES\n",tt);
		else printf("Case #%d: NO\n",tt);
	}
	
	return 0;
}
	

 

突然发现最小流板子居然忘了……补补bububububu

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值