poj 2195 最小费用最大流(小人回家)

题意:给定若干小人(用‘m’表示)和若干房子(用‘H’表示),已知小人的数量和房子的数量相同,一个房子只能容纳一个小人。问使得所有小人进入房子中走的最少距离是多少。

思路:最小费用最大流。小人和房子为顶点。每个小人i到房子j都连边,容量为1,费用为进入房子所需要的距离。最后再建立一个源点和一个汇点,源点连到所有的小人节点上,所有的房子节点连接到汇点上,这些边费用为0,容量为1.

#include <stdio.h>
#include <string.h>
#include <math.h>
#define INF 0x3fffffff
#define R 210
#define min(a,b) a<b?a:b
char s[R][R];
struct point{
	int x,y;
}man[R],house[R];//存储小人和房子的坐标
struct edge{
	int y,cost,cap,next;
}e[2*R*R+4*R];
int n,N,M,top,res;
int visited[R],dis[R],pre[R],index[R],first[R],q[2000000];
void add(int x,int y,int cost){//cost是边的费用,容量均为1
	e[top].y = y;
	e[top].cost = cost;
	e[top].cap = 1;
	e[top].next = first[x];
	first[x] = top++;
	e[top].y = x;
	e[top].cost = -cost;
	e[top].cap = 0;
	e[top].next = first[y];
	first[y] = top++;
}
int distance(int i,int j){//计算第i个小人到第j个房子的距离
	return abs(man[i].x-house[j].x)+abs(man[i].y-house[j].y);
}
int spfa(){
	int i,front,rear,now;
	front = rear = -1;
	for(i = 0;i<=n;i++)
		dis[i] = INF;
	memset(visited,0,sizeof(visited));
	memset(pre,-1,sizeof(pre));
	q[++rear] = 0;
	dis[0] = 0;
	while(front < rear){
		now = q[++front];
		visited[now] = 0;
		for(i = first[now];i!=-1;i=e[i].next)
			if(e[i].cap && dis[e[i].y]>dis[now]+e[i].cost){
				dis[e[i].y] = dis[now]+e[i].cost;
				if(!visited[e[i].y]){
					q[++rear] = e[i].y;
					visited[e[i].y] = 1;
				}
				pre[e[i].y] = now;
				index[e[i].y] = i;
			}
	}
	if(dis[n] == INF)
		return 0;
	return 1;
}
void update(){
	int i,aug=INF;
	for(i = n;i!=0;i=pre[i])
		aug = min(aug,e[index[i]].cap);
	res += aug*dis[n];
	for(i = n;i!=0;i=pre[i]){
		e[index[i]].cap -= aug;
		e[index[i]^1].cap += aug;
	}
}
int main(){
	freopen("a.txt","r",stdin);
	while(scanf("%d %d\n",&N,&M) &&N&&M){
		int i,j,a,b;
		memset(first,-1,sizeof(first));
		a = b = res = top = 0;
		for(i = 0;i<N;i++)
			scanf("%s",s[i]);
		for(i = 0;i<N;i++)
			for(j = 0;j<M;j++)
				if(s[i][j]=='m'){
					man[++a].x = i;
					man[a].y = j;
				}else if(s[i][j] == 'H'){
					house[++b].x = i;
					house[b].y = j;
				}
		n = a;
		for(i = 1;i<=n;i++)
			for(j = 1;j<=n;j++)
				add(i,j+n,distance(i,j));//小人和房子之间的边
		for(i = 1;i<=n;i++){
			add(0,i,0);
			add(n+i,2*n+1,0);//添加超级源点和汇点
		}
		n = 2*n+1;
		while(spfa())
			update();
		printf("%d\n",res);
	}
	return 0;
}


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值