poj3281-最大流

题目开始以为可以用二分匹配解决,但是要和两边都求最大匹配,没办法解决。但是想到最大流可以解决二分匹配问题,那么就建图用网络流解决。

但是一开始是这样建图源点-food-牛-drink-汇点,这样虽然满足每份food和drink只能给一头牛吃,但是没法解决每头牛只能吃一份的问题。

难在建图,如果是这样,源点-food-牛-牛-drink-汇点,将牛拆成两个点,里面的边权值全为1.用效率不是很高的Ek算法就能解决。EK算法比较好理解。

 

转:

最近又复习了下最大流问题,每次看这部分的内容都会有新的收获。可以说最大流问题的资料网上一搜一大把,根本没有必要自己写;但是大部分资料上的专业术语太多了,初学很难理解,至少我当年学这部分的时候前几次就没有看懂。所以我准备备份一点个人的理解。

poj3281-最大流_第1张图片图-1


 

  如图-1所示,在这个运输网络中,源点S和汇点T分别是1,7,各边的容量为C(u,v)。图中红色虚线所示就是一个可行流。标准图示法如图-2所示:

poj3281-最大流_第2张图片


 

其中p(u,v) / c(u,v)分别表示该边的实际流量与最大容量。

 

关于最大流

  熟悉了什么是网络流,最大流也就很好理解了。就是对于任意的u∈V-{s},使得p(s,u)的和达到最大。上面的运输网络中,最大流如图-3所示:MaxFlow=p(1,2)+p(1,3)=2+1=3。

poj3281-最大流_第3张图片

  在介绍最大流问题之前,先介绍几个概念:残余网络,增广路径,反向弧,最大流定理以及求最大流的Ford-Fulkerson方法。

残余网络 增广路径 反向弧

  
观察下图-4,这种状态下它的残余网络如图-5所示:

poj3281-最大流_第4张图片 poj3281-最大流_第5张图片


   

  也许现在你已经知道什么是残余网络了,对于已经找到一条从S 到T的路径的网络中,只要在这条路径上,把C(u,v)的值更新为C(u,v)-P(u,v),并且添加反向弧C(v,u)。对应的增广路径Path为残留网络上从S到T的一条简单路径。图-4中1,2,4,7就是一条增广路径,当然还有1,3,4,7。

  此外在未做任何操作之前,原始的有向图也是一个残余网络,它仅仅是未做任何更新而已。

 

最大流定理

  如果残留网络上找不到增广路径,则当前流为最大流;反之,如果当前流不为最大流,则一定有增广路径。

 

Ford-Fulkerson方法

  介绍完上面的概念之后,便可以用Ford-Fulkerson方法求最大流了。为什么叫Ford-Fulkerson方法而不是算法,原因在于可以用多种方式实现这一方法,方式并不唯一。下面介绍一种基于广度优先搜索(BFS)来计算增广路径P的算法:Edmonds-Karp算法。

  算法流程如下:

  设队列Q:存储当前未访问的节点,队首节点出队后,成为已检查的标点;

  Path数组:存储当前已访问过的节点的增广路径;

  Flow数组:存储一次BFS遍历之后流的可改进量;

  Repeat:

    Path清空;

    源点S进入Path和Q,Path[S]=0,Flow[S]=+∞;

    While Q非空 and 汇点T未访问 do

        Begin

            队首顶点u出对;

            For每一条从u出发的弧(u,v) do

                If v未访问 and 弧(u,v) 的流量可改进;

                Then Flow[v]=min(Flow[u],c[u][v]) and v入队 and Path[v]=u;

    End while

   

    If(汇点T已访问)

    Then 从汇点T沿着Path构造残余网络;

  Until 汇点T未被访问

代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#define inf 1000
#define nMax 410
#define Max(a,b) (a>b?a:b)
#define Min(a,b) (a<b?a:b)

int map[nMax][nMax];
int N,F,D;
int path[nMax];
int queue[nMax * 100];
int head,end;
//bool flag[nMax];
//广搜求一条增广路
int bfs()
{
	int minFlow = inf,u;
	memset(path, -1, sizeof(path));
	head = 0;
	end = 1;
	queue[head] = 0;
	while (head < end)
	{
		u = queue[head ++];
		if (u == 2 * N + F + D + 1)
		{
			break;
		}
		for (int i = 1; i <= 2 * N + F + D + 1; ++ i)
		{
			if (path[i] == -1 && map[u][i] )
			{
				if (minFlow > map[u][i])
				{
					minFlow = map[u][i];
				}
				queue[end ++] = i;
				path[i] = u;
				
			}
		}
	}
	if (path[2 * N + F + D + 1] == -1)
	{
		return -1;
	}
	return minFlow;
}
//EK算法,每次广搜得到一条增广路径,然后更新残留网络
void Edmods_Karp()
{
	int flow, maxFlow = 0, now, pre;
	while ((flow = bfs()) != -1)
	{
		maxFlow += flow;
		now = 2 * N + F + D + 1;
		while (now != 0)
		{
			pre = path[now];
			map[pre][now] -= flow;
			map[now][pre] += flow;
			now = pre;
		}
	}
	printf("%d\n", maxFlow);
}
//按照源点-食物-牛-牛-饮料-汇点的顺序建图
void buildMap()
{
	int fNum,dNum,fd;
	while (scanf("%d %d %d", &N, &F, &D) != EOF)
	{
		memset(map, 0, sizeof(map));
		//memset(flag, false, sizeof(flag));
		for (int i = 1; i <= N; ++ i)
		{
			scanf("%d %d", &fNum, &dNum);
			for (int j = 0; j < fNum; ++ j)
			{
				scanf("%d", &fd);
				map[0][fd] = 1;
				map[fd][i + F] = 1;
			}
			map[i + F][i + F + N] = 1;
			for (int j = 0; j < dNum; ++ j)
			{
				scanf("%d", &fd);
				map[fd + 2 * N + F][F + 2 * N + D + 1] = 1;
				map[i + F + N][fd + 2 * N + F] = 1;
			}
		}
		Edmods_Karp();
	}
}
//注意这里给点编号,0-源点,1-F是食物,F+1-F+N是牛左点,F+N+1-F+N+N是牛右点,F+N+N+1-F+N+N+D是drink饮料点,F+N+N+D+1是汇点

int main()
{
	buildMap();
	return 0;
}


 

你可能感兴趣的:(poj3281-最大流)