图论-网络流②-最大流①

图论-网络流②-最大流①

上一篇:图论-网络流①-什么是网络流

下一篇:图论-网络流③-最大流②

参考文献:

  • https://www.cnblogs.com/DuskOB/p/11216861.html
  • https://blog.csdn.net/yjr3426619/article/details/82808303
  • https://blog.csdn.net/lym940928/article/details/90209172
  • https://baike.baidu.com/item/%E7%BD%91%E7%BB%9C%E6%B5%81/2987528?fr=aladdin
  • https://www.cnblogs.com/pk28/p/8039645.html
  • https://blog.csdn.net/disgustinglemon/article/details/51296636

大纲

  • 什么是网络流
  • 最大流(最小割) Start \color{#33cc00}\texttt{Start}
  • D i n i c Dinic (常用) End \color{red}\texttt{End}
  • E K EK
  • S a p Sap
  • F o r d F u l k e r s o n Ford-Fulkerson (不讲)
  • H L P P HLPP (快)
  • 最大流解题
  • 费用流
  • S p f a Spfa 费用流
  • B e l l m a n F o r d Bellman-Ford 费用流
  • D i j k s t r a Dijkstra 费用流
  • z k w zkw 费用流
  • 费用流解题
  • 有上下界的网络流
  • 无源汇上下界可行流
  • 有源汇上下界可行流
  • 有源汇上下界最大流
  • 有源汇上下界最小流
  • 最大权闭合子图
  • 有上下界的网络流解题

最大流

源点到汇点的最大总流量。如果把网络流图比作水管道系统,那么就是求单位时间内源点到汇点能流多少水。概念较抽象,那么拿上面那张网络流图举例:
wll.jpg

s f l o w = 3 1 s\xRightarrow{flow=3} 1
s f l o w = 1 3 s\xRightarrow{flow=1} 3
1 f l o w = 1 2 1\xRightarrow{flow=1} 2
3 f l o w = 2 4 3\xRightarrow{flow=2} 4
1 f l o w = 2 4 1\xRightarrow{flow=2} 4
2 f l o w = 2 t 2\xRightarrow{flow=2} t
4 f l o w = 2 t 4\xRightarrow{flow=2} t

结论:最大流为 3 3

首先 s s 处可以流出无限流量,但因为 s s 1 1 的边流量只有 3 3 ,所以 s s 先向 1 1 3 3 的流量,现在 1 1 处有 3 3 的流量。因为是单位时间内的流量,所以 s 1 s\to1 的边就不能再用了。

同理, 1 1 处的 3 3 份流量分到 2 2 4 4 处。 1 2 1\to2 1 4 1\to4 的边不能再用。 2 2 处有 1 1 流量, 4 4 处有 2 2 流量。然后再通过边 2 t 2\to t 4 t 4\to t 向汇点 t t 3 3 的流量。

然后再用 s 3 s\to3 ,于是 3 3 处有了 1 1 的流量。然后再走 3 4 3\to 4 因为 3 3 处只有 1 1 的流量,所以虽然 3 4 3\to4 能流 2 2 的流量, 4 4 也只能得到 1 1 的流量。 然后再看 4 t 4\to t 因为 4 t 4\to t 的边已经用完,所以即使 4 4 处有 1 1 的流量, t t 也得不到一点流量。

所以最终最大流为 3 3

不过现在先不讲最大流算法,过会儿你就知道为什么了。

最小割

定义

给定一个网络流图 G = ( V , E ) G=(V,E) ,源点为 s s ,汇点为 t t 。若一个边集 E E E'⊆E 被删去后, s s t t 不再联通,则称该边集为网络的割。边流量之和的最小的割称为网络的最小割。

再回到刚刚那个网络流图:
wll.jpg
结论:最小割为 3 3

先割断 1 2 1\to2 ,再割断 4 t 4\to t s s t t 不再联通。并且保证没有别的割法比这样更优。所以该网络流图的最小割为 3 3

重要结论:

一个网络流图的最小割等于它的最大流。 \color{#aa55ff}\texttt{一个网络流图的最小割等于它的最大流。}

最大流(最小割)算法

通过上文我们已知一个网络流图的最小割等于它的最大流,所以求最大流的算法同样也是求最小割的算法。求最大流的算法很多,其中 D i n i c Dinic 最为普遍使用。

Dinic

D i n i c Dinic 算法会对网络流图做一个分层。其实也就是做一次 B f s Bfs ,把源点设为第 0 0 层,然后遍历源点的相邻节点,设为第 1 1 层,再遍历这些节点的相邻节点……直到汇点。

实现:

1.将源点的层次设为 0 0 ,把源点设为已经访问,别的点设为未访问。

2.按照 B f s Bfs 序遍历网络流图,为节点分层,遍历到终点结束。

3.在层次网络中,沿着相邻层 D f s Dfs 搜索所有的增广路,并做相应的流量调整。

4.重复 1 3 1\sim 3

算法较抽象,拿图举例(节点上的数为层次):

第一次建立层次网络,找到了蓝线表示的三条增广路,做流量调整。
在这里插入图片描述

第二次建立层次网络,上次流光的边这次不发挥作用,找到一条增广路。
在这里插入图片描述

第三次建立层次网络,汇点已经不能通过有用的边联通,算法终止。

在这里插入图片描述

如果你懂了,蒟蒻就放代码了。 D i n i c Dinic 最大流最小割算法:

#include <bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
template<int V,int M>
class Dinic{
public:int E,g[V],to[M],nex[M],fw[M];
	void clear(){memset(g,0,sizeof g),E=1;} 
	//E=1保证了互为反边的两条边可以通过^1互相得到
	Dinic(){clear();}
	//初始化
	void add(int x,int y,int f){nex[++E]=g[x],to[E]=y,fw[E]=f,g[x]=E;}
	//标准加边
	void Add(int x,int y,int f){add(x,y,f),add(y,x,0);}
	//加正边和反边,使得增广可以反悔
	int dep[V],cur[V];bool vis[V];queue<int> q;
	//dep表示层次,cur为单前弧优化,下面会讲。
	//vis表示是否访问,queue维护bfs
	bool bfs(int s,int t,int p){
		for(int i=1;i<=p;i++) vis[i]=0,cur[i]=g[i];
		q.push(s),vis[s]=1,dep[s]=0; //从源点开始bfs
		while(q.size()){
			int x=q.front();q.pop();
			for(int i=g[x];i;i=nex[i])if(!vis[to[i]]&&fw[i])
				q.push(to[i]),vis[to[i]]=1,dep[to[i]]=dep[x]+1;
				//bfs过程中顺便给每个节点标上层次。
		}
		return vis[t]; //表示联通
	}
	int dfs(int x,int t,int F){
		if(x==t||!F) return F;
		int f,flow=0;
		for(int&i=cur[x];i;i=nex[i]) //即i=g[x]
			if(dep[to[i]]==dep[x]+1&&(f=dfs(to[i],t,min(F,fw[i])))>0) //沿着层次增广
				{fw[i]-=f,fw[i^1]+=f,F-=f,flow+=f;if(!F) break;}
				//边的流量调整
		return flow; //一次增广的流量。
	}
	int dinic(int s,int t,int p){ //多次增广函数
		int res=0,f;
		while(bfs(s,t,p)) while((f=dfs(s,t,inf))) res+=f;
		return res;
	}
};
int n,m,s,t,p;
Dinic<10010,200010> net;
int main(){
	scanf("%d%d%d%d",&n,&m,&s,&t),p=n;
	for(int i=1,x,y,f;i<=m;i++){
		scanf("%d%d%d",&x,&y,&f);
		net.Add(x,y,f);
	}
	printf("%d\n",net.dinic(s,t,p));
	return 0;
}

以上代码读者可能有两个地方不懂:

1.反向反悔边

因为一条边的流量是通过以下代码调整的:

fw[i]-=f,fw[i^1]+=f,F-=f,flow+=f;if(!F) break;

所以可以把反悔看成走反边,以得到最大流。

2.单前弧优化

for(int i=1;i<=p;i++) vis[i]=0,cur[i]=g[i];
for(int&i=cur[x];i;i=nex[i])

因为 D i n i c Dinic 的增广是沿着加边顺序(严谨地说,是沿着加边反顺序)增广的,所以每一次增广时,前面几条边可能已经增广完了,这时,如果记录第一条没增广完的边,下一次增广从这条边开始,就方便、快很多。

下一篇会讲最大流的剩下 3 3 种算法。

祝大家学习愉快!

发布了26 篇原创文章 · 获赞 58 · 访问量 7628

猜你喜欢

转载自blog.csdn.net/KonnyWen/article/details/104344452