网络流-EK求最大流

网络流-EK求最大流

2021.8.17

网络流最大流解决什么问题?

网络流(network-flows)是一种类比水流的解决问题方法(摘自百度百科),最大流是求源点到汇点最大流量的方法。

算法原理

已知起点到终点,很容易想到深搜\广搜求最大值的方式,但是,在最大流问题中,会出现如下情况:

在这里插入图片描述

假设1为源点,4为汇点,若对其进行搜索,路径可能有1->2->3->4,随后标记数组有如下标记:

在这里插入图片描述

那么,此时,流经1-2线路的水流,汇入了3号节点最终流向4号节点,此时3-4路径已经被灌满,2-4路径没有水流流入,最终汇入汇点的水流量为1。

但是,对于上述情况,最大流应该是如下情形:

在这里插入图片描述

此时汇点流入流量为2.

那么,在建图顺序位置时,如何计算出最大的流量?

退回流量,构建增广路径,当流量汇入其他渠道并且有部分无法汇入汇点,那么就代表其应该回到原渠道,按照原渠道走,但是搜索起来肯定难以实现,因此需要建立一条相反的路径,让流过去的水流能有机会返回原来的渠道,并且最多返回流过去的水流。(不管水流走在哪里,只要流入汇点即可,不必求出其准确的路径)如下图:

在这里插入图片描述

如此,在1-3路径中的水流流入3号节点时,可以通过2-3路径流回2号节点,再从2-4路径流入4号汇点,此时便可得最大流量为2.(若1-3为2,则也只能退回1).
在这里插入图片描述

但是通常情况下,退流一次并不会得到最大流,因此需要多次搜索多次退流。在EK求最大流中,每次选择在部分流量汇入汇点后,对路径的可行流进行更改,构建增广路径。此时图中每个点的残留量不影响结果,因再次搜索时只计算从源点流出的流量。

对其搜索时,正常进行搜索即可:

bool bfs() {
	queue<int>q;
	memset(vis, 0, sizeof vis);
	q.emplace(S); vis[S] = true, d[S] = 0x3f3f3f3f;
	while (!q.empty()) {
		int t = q.front(); q.pop();
		for (int i = head[t]; !i; i = nexte[i]) {
			int k = to[i];
			if (!vis[k] && wei[i]) {
				vis[k] = 1;
				d[k] = min(d[t], wei[i]);
				pre[k] = i;
				if (k == T)return true;
				q.emplace(k);
			}
		}
	}
	return false;
}

其中,d数组记录路径最多流过的流量,pre数组记录路径编号,当成功汇入汇点,则跳出。

每次对路径进行更新时,则对上一步搜索到的路径进行退流:

int EK() {
	int res = 0;
	while (bfs()) {//搜索直到没有可行流
		res += d[T];
		for (int i = T; i != S; i = to[pre[i] ^ 1])//建图时,反向边编号比正向边多1
			wei[pre[i]] -= d[T], wei[pre[i] ^ 1] += d[T];//构建增广路径
	}
	return res;
}

最终返回的res即为最大流。
注:正向路径同样要减去相应的流量大小,因路径已经被部分或全部占用。

模板代码

代码

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<string>
#include<string.h>
#include<queue>

using namespace std;

int nexte[20005], wei[20005], to[20005], head[20005];
int n, m, S, T;
int idx;
int pre[20005], d[20005];
bool vis[20005];


void addedge(int u, int v, int c) {
	to[idx] = v, wei[idx] = c, nexte[idx] = head[u], head[u] = idx++;
	to[idx] = u, wei[idx] = 0, nexte[idx] = head[v], head[v] = idx++;
}

bool bfs() {
	queue<int>q;
	memset(vis, 0, sizeof vis);
	q.emplace(S); vis[S] = true, d[S] = 0x3f3f3f3f;
	while (!q.empty()) {
		int t = q.front(); q.pop();
		for (int i = head[t]; !i; i = nexte[i]) {
			int k = to[i];
			if (!vis[k] && wei[i]) {
				d[k] = min(d[t], wei[i]), pre[k] = i;
				if (k == T)return true;
				q.emplace(k);
				vis[k] = 1;
			}
		}
	}
	return false;
}

int EK() {
	int res = 0;
	while (bfs()) {
		res += d[T];
		for (int i = T; i != S; i = to[pre[i] ^ 1])
			wei[pre[i]] -= d[T], wei[pre[i] ^ 1] += d[T];
	}
	return res;
}

int main() {
	cin >> n >> m >> S >> T;
	memset(head, -1, sizeof head);
	for (int i = 0; i < m; i++){
		int a, b, c;
		cin >> a >> b >> c;
		addedge(a, b, c);
	}
	cout << EK() << endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值