最大流_FF思想_EK算法

原题链接

poj 1273

问题

图论中有一个重要问题,假设有一个水管网图,每根水管都规定了一个可以流的方向,从v号结点到u号结点最大允许通过的水量为c(v,u),有一个源点s存储了无限多的水,有一个汇点可以存储无限多的水。问:从源点流向汇点的水流的最大速率是多少?

在这里插入图片描述

思路

采用经典的EK算法,时间复杂度为O(n*m^2)n为结点数,m为边数,虽然上界很高,但是一般情况很能到上界,一般的情况要好的多。

四个概念,一个定理:

网络流的概念和定理非常多,为了不混淆,先只讲两个会用到的定理。
1.反向边:
这个概念很好理解,对于图中的每一条有向边,如果存在一条方向相反的边,这条边就是反向边。
2.可行流:
从s流向t的任意一个满足条件的流(不一定是最大流)。
在这里插入图片描述

3.残留网络:
在图中找到任意一条可行流,并将该可行流的路径上的每一条边都建立一个反向边。 反向边的权值是可行流的权值,然后将正向边的权值更新为原来的权值-可行流的权值。
在这里插入图片描述

4.增广路径:
在当前的图中,存在一条从s到t的可行流路径。

定理1:
如果f是最大流, G f G_f Gf中一定不存在增广路径。
证明:
因为 f 可 行 流 + f 增 广 路 径 = f 可 行 流 f_{可行流}+f_{增广路径}=f_{可行流} f+f广=f
如果f是最大流,要满足上式, f 增 广 路 径 = 0 f_{增广路径}=0 f广=0,所以不存在增广路径。

FF思想

这是一种找最大流的思想,但是他有很多中算法实现。
1.找到一条从s到t的可行流路径。
2.记录可行流的大小。
2.根据可行流路径建立增广网络。
3.重复上述1到3,直到没有增广路径。

FF思想有很多严格的证明方法,我这里采用最直观但是相对没那么严谨的方法。
假设s到t有两条最大流,分别如下。
但是在寻找最大流时,我们的运气不好,本来应该经过A点到达t点,但是却在A点进行了拐弯,从下面到达了t点。按理来说这样的话,下面的最大流我们永远也无法找到了。但是,我们有在找到一条可行流时,会建立反向边,所以,这时候s到B时,还可以往上走,抵消掉了A->B的水流,从而到达了t点。所以,这个算法可以找到最大流。
在这里插入图片描述

EK算法

EK算法,包括dinic算法都是基于FF思想的一种算法。是FF思想的具体实现,但是,EK算法是最简答的一种算法。
1.bfs遍历图,找到一条可行流,累加可行流。
2.更新残余网络。
3.判断是否存在增广路,如果存在,重复1,2。
4.输出累加的可行流,即最大流。原理参考 f 可 行 流 + f 增 广 路 径 = f 可 行 流 f_{可行流}+f_{增广路径}=f_{可行流} f+f广=f

代码:

#include<iostream> 
#include<queue>
#include<algorithm>
#include<cstring>
#define debug(x) cout<<x<<endl;

using namespace std;

typedef long long LL;

const int N = 205, M = 205, INF = 0x3f3f3f3f;

LL G[N][M];//图
LL flow[N], pre[N];//结点的流,前驱
int n, m;//结点数,边数
int bfs(int s,int t) {
    
    
	memset(pre, -1, sizeof pre);
	memset(flow, 0, sizeof flow);
	queue<int > q;
	flow[1] = INF;//代表最多可以流出的水量
	q.push(s);
	while (q.size()) {
    
    
		int v = q.front(); q.pop();
		for (int i = 1; i <= n; i++) {
    
    
			if (G[v][i] > 0&&pre[i]==-1) {
    
    
				q.push(i);
				pre[i] = v;
				flow[i] = min(flow[v],G[v][i]);
			}
		}
		if (v == t) break;
	}
	return flow[t];
}

LL EK(int s, int t) {
    
    
	LL tol = 0;
	LL  res;//存放流入汇点的数值
	while (1){
    
    
		//存在增广路
		res = bfs(s, t);
		if (res == 0)break;
		int p = t;
		while (p != s) {
    
    
			//更改残留图
			G[pre[p]][p] -= res;
			G[p][pre[p]] = res;//回流
			p = pre[p];
		}
		tol += flow[t];
	}
	return tol;
}

int main() {
    
    
	while (~scanf("%d%d", &m, &n)) {
    
    
		memset(G, 0, sizeof G);
		for (int i = 0; i < m; i++) {
    
    
			int a, b, c;
			scanf("%d%d%d", &a, &b, &c);
			G[a][b] += c;
		}
		printf("%lld\n", EK(1, n));
	}
	return 0;
}

/*
test1:
7 6
1 2 40
1 4 30
2 3 30
2 5 30
4 5 40
3 6 40
5 6 50

70

test2:
7 6
1 2 5
1 4 6
2 3 4
4 5 4
2 5 2
3 6 3
5 6 7

9
test 3
1 2
1 2 10
*/

猜你喜欢

转载自blog.csdn.net/qq_45931661/article/details/120163332