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

本文深入探讨了网络最大流的基本概念,包括源点、汇点、容量和流量等关键术语,详细解析了Ford-Fulkerson算法和Dinic算法的原理与实现。通过自来水输送网络的例子,阐述了最大流最小割定理,以及如何通过增广路求解最大流问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

模板题

P3376 【模板】网络最大流


举个例子

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

网络流基本术语

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

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

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

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

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

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

最大流最小割

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

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


易懂的算法(Ford-Fullerson)

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

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

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

代码区

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<string>
#include<fstream>
#include<vector>
#include<stack>
#include <map>
#include <iomanip>
#define bug cout << "**********" << endl
#define show(x,y) cout<<"["<<x<<","<<y<<"] "
//#define LOCAL = 1;
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll mod = 1e6 + 3;
const int Max = 1e5 + 10;

struct Edge {
	int to, next, flow;	//flow记录这条边当前的边残量
}edge[Max << 1];


int n, m, s, t;
int head[Max], tot;
bool vis[Max];

void init()
{
	memset(head, -1, sizeof(head));tot = 0;
}

void add(int u, int v, int flow)
{
	edge[tot].to = v;
	edge[tot].flow = flow;
	edge[tot].next = head[u];
	head[u] = tot++;
}

//向图中增加一条容量为exp的边(增广路)
int dfs(int u,int exp)
{
	if (u == t) return exp;			//到达汇点,当前水量全部注入
	vis[u] = true;					//表示已经到了过了
	for(int i = head[u] ; i != -1  ;i = edge[i].next)
	{
		int v = edge[i].to;
		if(!vis[v] && edge[i].flow > 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<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<string>
#include<fstream>
#include<vector>
#include<stack>
#include <map>
#include <iomanip>
#define bug cout << "**********" << endl
#define show(x,y) "["<<x<<","<<y<<"]"
//#define LOCAL = 1;
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll mod = 1e6 + 3;
const int Max = 1e5 + 10;

struct Edge {
	int to, next, flow;	//flow记录这条边当前的边残量
}edge[Max << 1];


int n, m, s, t;
int head[Max], tot;
int dis[Max];

void init()
{
	memset(head, -1, sizeof(head));tot = 0;
}

void add(int u, int v, int flow)
{
	edge[tot].to = v;
	edge[tot].flow = flow;
	edge[tot].next = head[u];
	head[u] = tot++;
}

bool bfs() //判断图是否连通
{
	queue<int>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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值