1. 程式人生 > >網路流之最大費用流

網路流之最大費用流

傳送門​​​​​​

題意

現在有  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