POJ-2195 Going Home (最小费用最大流初学 && 最大权二分匹配—KM算法)

Going Home
Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 16870 Accepted: 8636

Description

On a grid map there are n little men and n houses. In each unit time, every little man can move one unit step, either horizontally, or vertically, to an adjacent point. For each little man, you need to pay a $1 travel fee for every step he moves, until he enters a house. The task is complicated with the restriction that each house can accommodate only one little man. 

Your task is to compute the minimum amount of money you need to pay in order to send these n little men into those n different houses. The input is a map of the scenario, a '.' means an empty space, an 'H' represents a house on that point, and am 'm' indicates there is a little man on that point. 

You can think of each point on the grid map as a quite large square, so it can hold n little men at the same time; also, it is okay if a little man steps on a grid with a house without entering that house.

Input

There are one or more test cases in the input. Each case starts with a line giving two integers N and M, where N is the number of rows of the map, and M is the number of columns. The rest of the input will be N lines describing the map. You may assume both N and M are between 2 and 100, inclusive. There will be the same number of 'H's and 'm's on the map; and there will be at most 100 houses. Input will terminate with 0 0 for N and M.

Output

For each test case, output one line with the single integer, which is the minimum amount, in dollars, you need to pay.

Sample Input

2 2
.m
H.
5 5
HH..m
.....
.....
.....
mm..H
7 8
...H....
...H....
...H....
mmmHmmmm
...H....
...H....
...H....
0 0

Sample Output

2
10
28


思路:

       最小费用最大流初学,模板题;

       可以说是利用EK算法来确定最大流,利用SPFA算法判断存不存在可行流,并选择出最小费用的可行流;两者结合即可求出最小费用最大流;

       也可用最大权二分匹配KM算法解决,但须注意,这儿求的是最小花费,所以用KM时要把权值 * -1,最后再把结果 * -1即可,也当做模板题整理!


最小费用最大流代码:

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <queue>
#define INF 0x7fffffff
#define EN 200000
#define N 300

using namespace std;

struct Edge{
	int u, v;		// 起点,终点
	int w, c;		// 代价,流量
	int next;		// 类似单链表中的next指针
};

struct Node{
	int x;
	int y;
};

Edge edge[EN];		// 边
Node H[N], M[N];
int ep = 0; 
bool vis[N];
int pre[N];			// 保存i的父节点
int min_weight[N];	// 原SPFA最小距离(dis)数组,此处保存最小代价
int head[N];		// 临界表

void AddEdge(int u, int v, int w, int c)
{
	edge[ep].u = u;			// 建立正向边
	edge[ep].v = v;
	edge[ep].w = w;
	edge[ep].c = c;
	edge[ep].next = head[u];
	head[u] = ep ++;
	
	edge[ep].u = v;			// 建立反向边
	edge[ep].v = u;
	edge[ep].w = -w;
	edge[ep].c = 0;
	edge[ep].next = head[v];
	head[v] = ep ++;
}

bool SPFA(int src, int des)					// 根据流量求最小代价
{
	queue<int>q;
	memset(vis, 0, sizeof(vis));
	memset(pre, -1, sizeof(pre));
	for(int j = 0; j <= des; j ++){
		min_weight[j] = INF;
	}

	q.push(src);
	vis[src] = 1;
	min_weight[src] = 0;

	while(!q.empty()){
		int cur = q.front();
		vis[cur] = 0;
		q.pop();

		for(int i = head[cur]; i != -1; i = edge[i].next){
			int v = edge[i].v;
			if(edge[i].c > 0 && min_weight[cur] + edge[i].w < min_weight[v]){			// 必须有流量,并且代价更小
				min_weight[v] = min_weight[cur] + edge[i].w;
				pre[v] = i;
				if(!vis[v]){
					vis[v] = 1;
					q.push(v);
				}
			}
		}
	}

	if(min_weight[des] == INF)
		return 0;
	else
		return 1;
}

int MCMF(int src, int des)			// EK 最大流
{
	int ans = 0;
	while(SPFA(src, des)){
		int i, min_c = INF;		   // 最小流量
/*		for(i = des; i != src; i = edge[pre[i]].u){
			if(min_c > edge[pre[i]].c){
				min_c = edge[pre[i]].c;
			}
		}		*/
		min_c = 1;					// 此题最小流量一定是1

		ans += min_weight[des] * min_c;

		for(i = des; i != src; i = edge[pre[i]].u){
			edge[ pre[i] ].c -= min_c;
			edge[ pre[i]^1 ].c += min_c;		// 反向边与正向变是挨着的,所以此处异或即可
		}
	}

	return ans;
}

int main()
{
	char c;
	int i, j;
	int row, col;
	while(scanf("%d%d", &row, &col), row && col){
		int H_Num = 0, M_Num = 0;
		memset(head, -1, sizeof(head));		// 初始化临界表

		for(i = 0; i < row; i ++){
			for(j = 0; j < col; j ++){
				scanf(" %c", &c);
				if(c == 'H'){
					H_Num ++;
					H[H_Num].x = i;
					H[H_Num].y = j;
				}
				else if(c == 'm'){
					M_Num ++;
					M[M_Num].x = i;
					M[M_Num].y = j;
				}
			}
		}

		for(i = 1; i <= M_Num; i ++){		// 建图
			for(j = 1; j <= H_Num; j ++){
				int mh_min_weight = abs(M[i].x - H[j].x) + abs(M[i].y - H[j].y);
				AddEdge(i, M_Num + j, mh_min_weight, 1);
			}
		}

		int src = 0;						// 超级源点
		int des = M_Num + H_Num + 1;		// 超级汇点

		for(i = 1; i <= M_Num; i ++)		// 将超级源点和超级汇点加入到图
			AddEdge(src, i, 0, 1);
		for(i = M_Num + 1; i <= M_Num + H_Num; i ++)
			AddEdge(i, des, 0, 1);

		int ans = MCMF(src, des);
		printf("%d\n", ans);
	}

	return 0;
}

KM算法代码:

#include <stdio.h>
#include <string.h>
#include <math.h>
#define INF 0x7fffffff
#define N 120

struct Node{
	int x;
	int y;
};

int nx, ny;
int w[N][N];					// 保存权值
int lx[N], ly[N];				// 可行顶标
int pre[N], slack[N];			// 父节点,保存修改量
bool visx[N], visy[N];

bool DFS(int x)
{
	visx[x] = 1;
	for(int y = 0; y < ny; y ++){
		if(visy[y]){
			continue;
		}

		int t = lx[x] + ly[y] - w[x][y];
		if(t == 0){					// 相等子图
			visy[y] = 1;
			if(pre[y] == -1 || DFS(pre[y]) ){
				pre[y] = x;
				return 1;			// 找到增广路
			}
		}
		else if(slack[y] > t){		// 不在相等子图中slack 取最小的 
			slack[y] = t;
		}
	}

	return 0;						// 未找到增广路
}

int KM()
{
	int i, j;
	memset(pre, -1, sizeof(pre));
	memset(ly, 0, sizeof(ly));
	for(i = 0; i < nx; i ++){
		lx[i] = -INF;
		for(j = 1; j < ny; j ++){
			if(w[i][j] > lx[i]){
				lx[i] = w[i][j];
			}
		}
	}

	for(int x = 0; x < nx; x ++){
		for(i = 0; i < ny; i ++){
			slack[i] = INF;
		}

		while(1){
			memset(visx, 0, sizeof(visx));
			memset(visy, 0, sizeof(visy));

			if(DFS(x)){				// 找到增广路,为下一点增广,否则修改顶标
				break;
			}

			int d = INF;
			for(i = 0; i < ny; i ++){
				if(!visy[i] && d > slack[i])
					d = slack[i];
			}

			for(i = 0; i < nx; i ++){
				if(visx[i]){
					lx[i] -= d;
				}
			}

			for(i = 0; i < ny; i ++){
				if(visy[i]){
					ly[i] += d;
				}
				else{
					slack[i] -= d;		// 修改顶标后,要把所有不在交错树中的Y顶点的slack值都减去d  
				}
			}
		}
	}

	int ans = 0;
	for(i = 0; i < ny; i ++){
		if(pre[i] >= 0)
			ans += w[pre[i]][i];
	}

	return ans;
}

int main()
{
	char c;
	int x, y;
	Node H[N], M[N];
	while(scanf("%d%d", &x, &y), x && y){
		memset(w, 0, sizeof(w));
		nx = ny = 0;

		int i, j;
		for(i = 0; i < x; i ++){
			for(j = 0; j < y; j ++){
				scanf(" %c", &c);
				if(c == 'H'){
					H[nx].x = i;
					H[nx].y = j;
					nx ++;
				}
				else if(c == 'm'){
					M[ny].x = i;
					M[ny].y = j;
					ny ++;
				}
			}
		}

		int dis;
		for(i = 0; i < nx; i ++){				// 建图
			for(j = 0; j < ny; j ++){
				dis = abs(H[i].x - M[j].x) + abs(H[i].y - M[j].y);
				w[i][j] = - dis;
			}
		}

		int ans = KM();
		printf("%d\n", -ans);
	}

	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值