vectorwyx 的博客

vectorwyx 的博客

My left brain has nothing right, my right brain has nothing left.

【学习笔记】最大流

posted on 2021-01-07 22:19:49 | under 未分类 |

(本博客同步于CSDN

非天赋型选手学了三天总算是在rqy神仙和CYjian神仙的帮助下(特别感谢!)弄明白了最大流和dinic算法(我好菜啊/ll)……特地来写一篇学习笔记


流网络流函数啥的oi-wiki上和xht的博客以及这篇博客已经讲的足够明白了,这里我只讲解比较难理解的、而且我认为前三者讲的不太明白的部分。可能有些地方不太严谨,求轻喷>_<。

增广路与反向边

增广路是指由S到T的、由剩余流量大于0的边组成的一条路径。一条增广路的权值等于其包含的路径的剩余流量的最小值。比如下图标红的就是一条增广路(从别人那里借来的):

可以看出,每找出一条增广路,我们所求的流量就会加上这条增广路的权值 $v$,并且增广路上的每条边的流量都会增加 $v$,相应地,剩余流量会减少 $v$。我们把这个求增广路再更新的过程称作一次增广。那么,如果我们不断增广直到无法找出增广路,此时的流量会不会就是我们要求的最大流呢?

答案是肯定的,只不过我们要引入新的东西:反向边。

反向边是指对于流网络上的每一条边(x,y),我们在连边的同时也将其反向边(y,x)加进去,并将其容量设为0。由流函数的斜对称性可知,若(x,y)这条边上的流量大于0,那么(y,x)的剩余流量一定也大于0。这是上图加了反向边后的模样:

在不加反向边之前,我们在增广完S->a->b->T这条增广路之后图上就不存在增广路了,但很显然还有更优的方法:S->a->T,S->b->T。这时候反向边就派上用场了,在增广完S->a->b->T后,我们还会再对s->b->a->T做一次增广,这样的效果就和S->a->T,S->b->T一样了。

但反向边就一定正确吗?我们可以把整个流网络比作一个输水系统来理解。如果一条权值为v的增广路包含一条反向边(y,x),其含义就是,我让x收回v的水流,再把这v的水流通过增广路的下半部分也就是x->T的部分汇入T;那从这条增广路的上半部分也就是S->y流过来的水流该怎么办呢?没关系,之前x不是从y这里收回了v的水流嘛,那这些已收回的水流原来是怎么从y到T的,我们就让新来的水流循着它们的路径从y到T。

反向边在这里起到的作用就是反悔和修正。它把增广路“割”成了上半部分和下半部分,又把一条已有的路径“割”成了上半部分和下半部分,然后又把这四个部分交叉地拼成了两条路径。这样,就保证了我们最终增广出来的答案一定为最大流。

所谓EK算法,就是暴力地用bfs不断求出增广路并进行增广。弄明白上述内容后,EK算法也就不在话下了。

dinic与当前弧优化

dinic与EK的区别有三个:

  • 采用dfs找增广路,但仍然只增广最短的。这个过程需要先用bfs将残量网络分层,求出每个点x到S的最短距离d[x]作为其层数。然后在dfs时只向下一层的结点扩展。

  • 多路增广。挺有效的一个优化手段,就是说在dfs时一次性把当前结点所能扩展出去的增广路全部增广。也就是说我们的增广对象由一条链变为了一张类似于dfs树的“dfs图”。

  • 当前弧优化

当前弧优化是dinic算法复杂度的保证,别看它名字很高大上,核心思想其实很简单。就是我们在多路增广时会遍历点x的出边序列,假设遍历到第i条出边时我们的“限额”已经用光了,即当前dfs走的S->x的路径已经出现瓶颈边(瓶颈边就是指剩余流量为0的边),我们需要终止递归并回溯。那下次搜索时再走到x,我们不需要从出边序列的开头遍历,可以跳过前i-1条出边直接从第i条出边开始遍历。为什么呢?因为前i-1条出边所能扩展出的所有可能组成增广路的路径一定都出现了瓶颈边,不能再组成增广路了。毕竟每条增广路在增广之后都会出现至少一条瓶颈边,由于多路增广是在第i条出边时终止的,也就是说在遍历到第i条出边时S->x这一部分才出现了瓶颈边,之前并没有出现。那前i-1条出边的所有扩展出的增广路的瓶颈边就一定会落在下半部分也就是x->T这一部分,所以在此之后就不可能再发生增广了。

把这三点加上,魔改亿下EK就可以轻松搞定dinic了。


应iee巨佬的要求,这是我自己写的EK和dinic的参考代码,请查收qwq。