HDU4332 Candy(经典贪心拆边费用流)

链接传送门

2012年多校的一道费用流,令人叹为观止的贪心建图思想

首先一块糖果给不同人的收益是不同的,所以考虑费用流而不是最大流

然后就不会做了…

发现糖果要么贡献价值 k k k,要么贡献价值 1 1 1

如果我们让尽量多的糖果贡献价值 k k k,是不是意味着最优呢?

大概是这样,但也不全是,因为有的人会溢出收益

考虑某个人的 b i b_i bi,如果 b i % k = = 0 b_i\%k==0 bi%k==0,那么他最多能吃掉 b i / k b_i/k bi/k个喜欢的糖果

那 么 这 个 人 向 汇 点 连 一 条 流 量 b i / k , 费 用 k 的 边 那么这个人向汇点连一条流量b_i/k,费用k的边 bi/k,k

如果 b i % k ! = 0 b_i\%k!=0 bi%k!=0,那么他最多吃掉 b i / k + 1 b_i/k+1 bi/k+1个糖果,但这个时候稍有变化

最后一枚糖果的收益溢出了,那我们应该减少最后一枚糖果的收益

所 以 这 个 人 向 汇 点 连 一 条 流 量 b i / k , 费 用 k 的 边 所以这个人向汇点连一条流量b_i/k,费用k的边 bi/k,k

再 向 汇 点 额 外 连 一 条 流 量 1 , 费 用 b i % k 的 边 再向汇点额外连一条流量1,费用b_i\%k的边 1,bi%k

这样跑最大费用最大流,得出来的是

当花费最多喜爱的糖果数时,造成的最大费用

此 时 的 最 大 流 m a x f l o w 是 使 用 的 喜 爱 糖 果 的 最 大 数 目 , 很 明 显 这 个 数 应 该 最 大 ( 因 为 不 用 价 值 就 是 1 ) 此时的最大流maxflow是使用的喜爱糖果的最大数目,很明显这个数应该最大(因为不用价值就是1) maxflow使,(1)

此 时 的 最 大 费 用 m a x c o s t 是 在 最 大 流 的 基 础 的 出 来 的 , 所 以 必 然 最 优 秀 此时的最大费用maxcost是在最大流的基础的出来的,所以必然最优秀 maxcost,

那 么 还 剩 n − m a x f l o w 个 糖 果 , 每 颗 可 以 贡 献 1 个 价 值 那么还剩n-maxflow个糖果,每颗可以贡献1个价值 nmaxflow,1

设 b i 的 总 和 是 s u m n , 那 么 还 有 s u m n − m a x c o s t 个 单 位 价 值 需 要 被 填 充 设b_i的总和是sumn,那么还有sumn-maxcost个单位价值需要被填充 bisumn,sumnmaxcost

所 以 判 断 下 n − m a x f l o w > = s u m n − m a x c o s t 即 可 所以判断下n-maxflow>=sumn-maxcost即可 nmaxflow>=sumnmaxcost

代码是用的最小费用最大流

#include <bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
const int inf=1e9;
int n,m,s,t,k;
int maxflow,mincost,b[maxn];
int dis[maxn],head[maxn<<1],cnt=1,incf[maxn],pre[maxn],vis[maxn];
struct edge{
	int to,nxt,flow,w;//分别代表 
}d[maxn<<1];
void add(int u,int v,int flow,int w)//最大流量,单位费用
{
	d[++cnt]=(edge){v,head[u],flow,w},head[u]=cnt;
	d[++cnt]=(edge){u,head[v],0,-w},head[v]=cnt;
} 
bool spfa()
{
	queue<int>q;
	for(int i=0;i<=t;i++)	dis[i]=inf,vis[i]=0;
	q.push(s);
	dis[s]=0,vis[s]=1;
	incf[s] = inf;//初始流量无限大
	while( !q.empty() )
	{
		int u=q.front(); q.pop();
		vis[u]=0;//出队
		for(int i=head[u];i;i=d[i].nxt)
		{
			if( !d[i].flow )	continue;//无流量了	
			int v=d[i].to;
			if( dis[v]>dis[u]+d[i].w )
			{
				dis[v]=dis[u]+d[i].w;
				incf[v] = min(incf[u],d[i].flow);//更新当前流量
				pre[v]=i;//记录从哪条边过来的
				if( !vis[v] )	vis[v]=1,q.push(v); 
			}
		}	
	} 
	if( dis[t]==inf )	return 0;
	return 1;
}
void dinic()
{
	while( spfa() )
	{
		int x=t,i ;//倒回去找路径
		maxflow+=incf[t],mincost+=dis[t]*incf[t];
		while(x != s)
		{
			i=pre[x];
			d[i].flow-=incf[t],d[i^1].flow+=incf[t];//加上流量
			x = d[i^1].to;//因为是倒回去,所以利用反向边倒回去 
		 } 
	}
}
int main()
{
	int T; cin >> T;
	int casenum=0;
	while( T-- )
	{
		cin >> n >> m >> k;
		s=0,t=n+m+1;
		mincost=maxflow=0;
		int sumn=0;
		for(int i=1;i<=m;i++)
		{
			cin >> b[i];
			sumn+=b[i];
			add(i+n,t,b[i]/k,-k);
			if( b[i]%k==0 )	continue;
			add(i+n,t,1,-b[i]%k );
		}
		for(int i=1;i<=n;i++)	add(s,i,1,0);
		for(int i=1;i<=m;i++)
		for(int j=1;j<=n;j++)
		{
			int x; cin >> x;
			if( x )	add(j,i+n,1,0);
		}
		dinic();
		printf("Case #%d: ",++casenum);
		if( n-maxflow>=sumn+mincost )	printf("YES\n");
		else	printf("NO\n");
		cnt=1;
		for(int i=s;i<=t;i++)	head[i]=0;
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值