Bootstrap

网络流最大流----EK算法

先来介绍一些基本概念:

网络是指一个有向图G=(V,E),有两个特殊节点:源点S和汇点T。每条有向边(x,y)都有一个权值c(x,y),称为边的容量。如果(x,y)不在图中,那么就有c(x,y)=0.

用f(x,y)表示边(x,y)上的流量,那么c(x,y)-f(x,y)就是边的剩余容量。

通常用f(x,y)/c(x,y)的形式标记边上的流量与容量。

可行流应该满足:

1.容量限制:f(x,y)<=c(x,y)

2.流量守恒:

最大流:从源点流向汇点的最大流量

增广路:一条从源点到汇点的所有边的剩余容量>=0的路径。

残留网:由网络中所有结点和剩余容量大于0的边构成的子图,这里的边包括有向边和其反向边。

建图时每条有向边(x,y)都构建一条反向边(y,x),初始容量c(y,x)=0.构建反向边的目的就是提供一个”退流管道“,一旦前面的增广路堵死可行流,可以通过”退流管道“退流,提供了后悔机制。

EK算法步骤:

先bfs找增广路:

过程:

1.初始化,mf[s]=无穷大,也就是默认起点不限流,将s入队

2.只要队列不空,就将其队头节点出队,并用其进行扩展节点,被其扩展的节点都要更新其最大限制流量并记录每个节点扩展路径的前驱边的编号,便于后续对残留图进行修改,当遇到汇点t时返回true

3.如果bfs过程中没有遇到汇点t,最后返回false

EK求最大流

循环找增广路,如果没有找到增广路就直接返回当前流量的累加值,若找到了增广路则说明流量可以继续扩展,最后返回最大流

复杂度是O(n*m*m)

一些技巧:

1.由于图中可能出现重边,根据复杂度的表达式可以看出重边会增大算法的复杂度,所以我们可以预处理一下给的边权,让其相同边的边权先进行累加最后统一建图

2.建图时我们不仅要建正向边,还要建立反向边,我们可以让正向边和反向边的编号只是2进制中的最低位不同,这样我们就能直接通过编号异或而得到反向边的编号了

3.求解最大流时一开始给定的边中不能具有双向边,如果有双向边,我们可以新加一个节点,使其将双向边变为等价的单向边,例:

给出一道例题:洛谷P3376

题目链接:【模板】网络最大流 - 洛谷

题目:

样例输入: 

4 5 4 3
4 2 30
4 3 20
2 3 20
2 1 30
1 3 40

样例输出:

50

细节见代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
#define int long long
const int N=2e5+10,M=203;
int e[N],ne[N],idx,h[N],w[N],p[M][M];//p数组用来处理重边 
int pre[M],mf[M];//pre[i]记录i号节点的前驱边的编号,mf[i]记录第i个节点的最大扩增流量 
bool vis[M];
int n,m,s,t; 
void add(int x,int y,int z)
{
	e[idx]=y;
	w[idx]=z;
	ne[idx]=h[x];
	h[x]=idx++;
}
bool bfs()//寻找增广路 
{
	memset(vis,false,sizeof vis);
	mf[s]=0x3f3f3f3f;//将起点的扩增流量设置为无穷大 
	queue<int>q;
	q.push(s);
	while(!q.empty())
	{
		int x=q.front();
		q.pop();
		for(int i=h[x];i!=-1;i=ne[i])
		{
			int j=e[i];
			if(!vis[j]&&w[i])
			{
				vis[j]=true;
				mf[j]=min(mf[x],w[i]);//该点的最大扩增流量为扩增路径中所有点的最大限制流量的最小值 
				pre[j]=i;//记录前驱边的编号
				q.push(j);
				if(j==t) return true;
			}
		}
	}
	return false;
} 
int EK()
{
	int ans=0;
	while(bfs())
	{
		int x=t;
		while(x!=s)
		{
			int id=pre[x];
			w[id]-=mf[t];//减去终点的新增流量
			w[id^1]+=mf[t];//反向边加上终点的新增流量 
			x=e[id^1];//反向边的末节点就是x的前驱节点
		}
		ans+=mf[t];//答案记录本次增广路新增流量 
	}
	return ans;
}
signed main()
{
	cin>>n>>m>>s>>t;
	for(int i=1;i<=n;i++) h[i]=-1;
	for(int i=1;i<=m;i++)
	{
		int u,v,z;
		scanf("%lld%lld%lld",&u,&v,&z);
		p[u][v]+=z;//处理重边 
	}
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
		if(p[i][j])
		{
			add(i,j,p[i][j]);
			add(j,i,0);//建立反向边 
		}
	printf("%lld",EK());
	return 0;
}
;