网络流-最大流简述(模板题+基础Ford-Fullerson+常用Dinic)

模板题

P3376 【模板】网络最大流


举个例子

自来水公司和自己家里的水龙头之间是由多条管道连接的,一般情况下,这些管道的大小是不同的,即单位时间内运输的水量是不同的,而且由自来水公司到家里的水龙头之间的输水通道也不会只有一条,往往成复杂的网络状。在这种情况下,求单位时间内家中水龙头最多可以得到多少的水。

网络流基本术语

源点:数据传输过程中的最初点,相当于例子中水源的起点

汇点:传输过程中,数据最终的汇聚的点,相当于例子中家里的水龙头

容量:每两个点之间最大可传输数据量,相当于例子中的管道大小

流量:两个点之间实际传输的数据量,相当于例子中管道实际运输的水量

残余网络:这个地方不好说明,先记为是当前的图的中所有的容量不为0的边吧

增广路:在当前残余网络上由源点s到汇点t的路径

最大流最小割

我们知道每一条增广路径都有一个容量最小的边限制这一增广路径的流量,而我们删除增广路上容量最小的那一条边,就可以用最小的代价使得这一增广路不连通

如果我们将整个图中所有增广路中容量最小的边全部删除,将得到最小割,显然,所有增广路中容量最小的边的容量之和即为最大流,因此,最小割即为最大流


易懂的算法(Ford-Fullerson)

求最大流的时候并不能由贪心思想得到,如何分配好每条边实际流量大小至关重要,根据Ford-Fullerson算法的思想,我们的执行步骤如下:

1)在由残余网络和点组成的图中寻找一条增广路,记这一增广路的流量为flow(增广路上边的容量最小者),执行下一步。如果找不到增广路,那么就结束这个循环,并返回所有增广路的流量之和 {flow}。

2)将这一增广路上所有边的容量都减去flow,同时将每条边对应的反边的容量加上flow,得到新的残余网络,重复第一步操作

代码区

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include 
#include 
#define bug cout << "**********" << endl
#define show(x,y) cout<<"["< 0)
		{
			int flow = dfs(v, min(exp, edge[i].flow));
			if(flow > 0)			//形成了增广路
			{
				edge[i].flow -= flow;
				edge[i ^ 1].flow += flow;
				return flow;
			}

		}

	}
	return 0;						//无法形成增广路的情况
}

//求最大流
int max_flow()
{
	int flow = 0;
	while(true)
	{
		memset(vis, 0, sizeof(vis));
		int part_flow = dfs(s, inf);
		if (part_flow == 0) return flow;
		flow += part_flow;
	}
}



int main() {
#ifdef LOCAL
	freopen("input.txt", "r", stdin);
	freopen("output.txt", "w", stdout);
#endif
	while (scanf("%d%d%d%d", &n, &m, &s, &t) != EOF)
	{
		init();
		for (int i = 1, u, v, flow;i <= m; i++)
		{
			scanf("%d%d%d", &u, &v, &flow);
			add(u, v, flow);add(v, u, 0);
		}
		printf("%d\n", max_flow());
	}

	return 0;
}

更高效的算法(Dinic)

以下的代码是没有加当前弧优化的Dinic算法,相比于上面的那种方法,此处我们对图实现了分层,这样一来,我们就可以实现一次bfs增广多条增广路了。

代码模板

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include 
#include 
#define bug cout << "**********" << endl
#define show(x,y) "["<q;
	memset(dis, -1, sizeof(dis));
	dis[s] = 0;
	q.push(s);
	while (!q.empty())
	{
		int u = q.front();q.pop();
		for (int i = head[u]; i != -1; i = edge[i].next)
		{
			int v = edge[i].to;
			if (dis[v] == -1 && edge[i].flow > 0)		//可以借助边i到达新的结点
			{
				dis[v] = dis[u] + 1;					//求顶点到源点的距离编号
				q.push(v);
			}
		}
	}
	return dis[t] != -1;								//确认是否连通
}

int dfs(int u, int flow_in)
{
	if (u == t) return flow_in;
	int flow_out = 0;									//记录这一点实际流出的流量
	for (int i = head[u]; i != -1;i = edge[i].next)
	{
		int v = edge[i].to;
		if (dis[v] == dis[u] + 1 && edge[i].flow > 0)
		{
			int flow_part = dfs(v, min(flow_in, edge[i].flow));
			if (flow_part == 0)continue;	//无法形成增广路
			flow_in -= flow_part;						//流出了一部分,剩余可分配流入就减少了
			flow_out += flow_part;						//记录这一点最大的流出

			edge[i].flow -= flow_part;
			edge[i ^ 1].flow += flow_part;				//减少增广路上边的容量,增加其反向边的容量
			if (flow_in == 0)
				break;
		}
	}
	return flow_out;
		}


int main() {
#ifdef LOCAL
	freopen("input.txt", "r", stdin);
	freopen("output.txt", "w", stdout);
#endif
	while (scanf("%d%d%d%d", &n, &m, &s, &t) != EOF)
	{
		init();
		for (int i = 1, u, v, flow;i <= m; i++)
		{
			scanf("%d%d%d", &u, &v, &flow);
			add(u, v, flow);add(v, u, 0);
		}
		int sum = 0;
		while (bfs())
		{
			sum += dfs(s, inf);
		}
		printf("%d\n", sum);
	}

	return 0;
}

 

你可能感兴趣的:(网络流)