是什么是网络流

在一个有向图上选择一个源点,一个汇点,每一条边上都有一个流量上限(以下称为容量),即经过这条边的流量不能超过这个上界,同时,除源点和汇点外,所有点的入流和出流都相等,而源点只有流出的流,汇点只有汇入的流。这样的图叫做网络流。

一些定义

源点:只有流出去的点
汇点:只有流进来的点
流量:一条边上流过的流量
容量:一条边上可供流过的最大流量
残量:一条边上的容量-流量

网络流最大流

定义

网络流的最大流算法就是指的一个流量的方案使得网络中流量最大。

网络流最大流求解

网络流的所有算法都是基于一种增广路的思想,下面首先简要的说一下增广路思想,其基本步骤如下:

  1. 找到一条从源点到汇点的路径,使得路径上任意一条边的残量>0(注意是小于而不是小于等于,这意味着这条边还可以分配流量),这条路径便称为增广路
  2. 找到这条路径上最小的F[u][v](我们设F[u][v]表示u->v这条边上的残量即剩余流量),下面记为flow
  3. 将这条路径上的每一条有向边u->v的残量减去flow,同时对于起反向边v->u的残量加上flow(为什么呢?我们下面再讲)
  4. 重复上述过程,直到找不出增广路,此时我们就找到了最大流

这个算法是基于增广路定理(Augmenting Path Theorem): 网络达到最大流当且仅当残留网络中没有增广路.

为什么要建反边

举个栗子:
先找增广路,比如说我们走了s->3->5->t,那么图会变成:
网络流Dinic算法_#include
再找一次增广路,这次比如说我们走s->4->5->t。
网络流Dinic算法_网络流_02
(那个65被洛谷打了美观的水印,将就看吧)

再找一次增广路,这次我们找s->4->5->3->t
网络流Dinic算法_#define_03

好像有点奇怪。你可能会想:为什么这次搜索的时候走了反向边(红色)的边,为什么走了反向边(红色)的边会加正向边(黑色)的边?

感性理解一下,就好像有45个人通过s->4->5->t到达汇点。
但是到5点时,我们发现有十个人来自3点,两股人流无法通过5->t,
于是我们通过反边发现3点来的人可以直接3->t到达汇点,这样安排,
就可以使得人流量最大。

那反边的意义就是:
我们知道,当我们在寻找增广路的时候,在前面找出的不一定是最优解,如果我们在减去残量网络中正向边的同时将相对应的反向边加上对应的值,我们就相当于可以反悔从这条边流过。
比如说我们现在选择从u流向v一些流量,但是我们后面发现,如果有另外的流量从p流向v,而原来u流过来的流量可以从u->q流走,这样就可以增加总流量,其效果就相当于p->v->u->q

简单来说就是:
留下一个标记,让后面的人有机会让前面的人走另一条路。

朴素算法的低效之处

虽然说我们已经知道了增广路的实现,但是单纯地这样选择可能会陷入不好的境地,比如说这个经典的例子:
网络流Dinic算法_网络流最大流_04
我们一眼可以看出最大流是999(s->v->t)+999(s->u->t),但如果程序采取了不恰当的增广策略:s->v->u->t
网络流Dinic算法_最大流_05
我们发现中间会加一条u->v的边
网络流Dinic算法_网络流_06
下一次增广的时候:
网络流Dinic算法_最大流_07
若选择了s->u->v->t
网络流Dinic算法_#include_08

然后就变成
网络流Dinic算法_网络流_09

这是个非常低效的过程,并且当图中的999变成更大的数时,这个劣势还会更加明显。

怎么办呢?

这时我们引入Dinic算法

为了解决我们上面遇到的低效方法,Dinic算法引入了一个叫做分层图的概念。具体就是对于每一个点,我们根据从源点开始的bfs序列,为每一个点分配一个深度,然后我们进行若干遍dfs寻找增广路,每一次由u推出v必须保证v的深度必须是u的深度+1。

Dinic算法具体步骤如下:
  1. 初始化容量网络和网络流。
  2. 构造残留网络和层次网络,若汇点不再层次网络中,则算法结束。
  3. 在层次网络中用一次DFS过程进行增广,DFS执行完毕,该阶段的增广也执行完毕。
  4. 转步骤2。

下面给出Dinic算法的代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#define ll long long
#define inf 0x3f3f3f3f

using namespace std;

const ll N = 2e5 + 10;
struct node {ll to, next, w;} e[N]; 
ll n, m, s, t, u, v, w, cnt = 1, head[N], maxflow, d[N], now[N];

void add(ll x, ll y, ll z)
{
	e[++cnt] = (node){y, head[x], z};
	head[x] = cnt;
}

bool bfs()
{
	queue <ll> q;
	memset(d, 0, sizeof(d));
	while(!q.empty()) q.pop();
	q.push(s), d[s] = 1, now[s] = head[s];
	while(!q.empty())
	{
		ll x = q.front(); q.pop();
		for(ll i = head[x]; i; i = e[i].next)
		{
			ll v = e[i].to;
			if(e[i].w && !d[v])
			{
				d[v] = d[x] + 1;
				q.push(v);
				now[v] = head[v];
				if(v == t) return 1;
			}
		}
	}
	return 0;
}

ll dinic(ll x, ll flow)
{
	if(x == t) return flow;
	ll rest = flow, i, k;
	for(i = now[x]; i && rest; i = e[i].next)
	{
		ll v = e[i].to;
		if(e[i].w && d[v] == d[x] + 1)
		{
			k = dinic(v, min(rest, e[i].w));
			if(!k) d[v] = 0;
			e[i].w -= k;
			e[i ^ 1].w += k;
			rest -= k;
		}
	}
	now[x] = i;
	return flow - rest;
}

int main()
{
	scanf("%lld%lld%lld%lld", &n, &m, &s, &t);
	for(ll i = 1; i <= m; i++)
		scanf("%lld%lld%lld", &u, &v, &w), add(u, v, w), add(v, u, 0);
	while(bfs())
	 while(ll res = dinic(s, inf)) 
		maxflow += res;
	printf("%lld\n", maxflow);
	return 0;
}

题目来源:link

十年OI的梦想,一朝AK的奢望