• 网络流初步详解


    众所周知,网络流是探究网络上运输的一种图论分支。但是大多数人在第一次接触这个题时都有些畏惧感(比如说我),大佬可以自信跳过..


    本文包括:

    1.网络流的概念及基本性质

    2.略谈 Edmonds-Karp增广路算法

    3.详谈 Dinic 算法

    4.网络流的应用以及ISAP算法引入


    1 . 网络流的概念及基本性质

    网络流是图论的一种重要分支,我们可以将网络流初步理解为一种 水道 一样的网络。

    基本定义: 部分参考《算法竞赛进阶指南》)

    对于一个网络 (G = (V , E )) 为一张有向图,图中的每条有向边 $ ( X,Y)∉E $ , 则 $ C(X,Y) = 0 $ 。图中还有两个节点 $ S , T $ 十分特殊,我们将其称为 源点汇点

    我们将 (f) 函数称作网络的流函数

    对于 ((X,Y)∈E , f(X,Y)) 称为边的流量 ,$ C(X,Y) - f(X,Y)$ 称为边的剩余流量。

    知道大佬们都不想看, 那么直接一点。

    假设有一片有向的水域,有多条有向的河流,河流(S) 点出发,最终汇向 $ T$ 点。每条河流有一定的宽度,只允许最多不超过该条河流边权值 的水流通过。

    如上图 ,蓝点为源点 , 红点为汇点。而紫色点紫色边显然无用,选择不流过紫色。

    ** 默认 S 点的水量有无限多。 **

    在初步了解后,按照流函数的定义,一个网络中的每条边实际上都有一条反向边,并且这些反向边都有一个负的流量, 这个定义将有助于我们解决之后的回溯问题

    基本性质:

    1.容量限制

    任意一条边的流量必定小于它的容量(及它的边权)。

    2.斜对称定理

    一条边((X,Y))(X)(Y) 的流量必定与其反边((Y,X))(Y)(X) 的流量相反。

    3.流量守恒定理

    除了源点 $ S $ 和 汇点 $ T $外 ,任意节点的 流入总量 都等于 流出总量 。即不会存储流量 。


    2.略谈 Edmonds-Karp增广路算法

    学习过匈牙利算法的同学已经了解增广路的定义,没有的话建议先 $ A$ 掉P3386 【模板】二分图匹配
    ,将有利于理解本文(貌似没有关联)。

    但是增广路此时的定义为:

    一条增广路从源点 (S) 到汇点 (T) 的路径上各边的剩余流量的最小值大于 0

    Edmonds-Karp增广路算法(下列简称EK算法)的思路就是对该网络进行BFS,不断找出其增广路,直至将该网络上的所有增广路全部找出。

    EK算法的正确性显然,在这里就不给出详细证明。(有兴趣者可参考《算法竞赛进阶指南》)。

    EK算法具体实现过程如下:

    1 . 用BFS在网络上寻找可行增广路

    2 . 在BFS找到任意一条增广路时计算出该增广路上各边剩余流量最小值 min 。

    3 . 最大流 (maxflow) 的值增加 min ,如此往复直至BFS找不增广路。

    给出图 及 图的最大流 7 作参考

    代码暂未上传。

    但是EK算法存在明显的局限性,及每次更新都要从头到尾BFS一下,只能解决 (O(nm^2)) 的网络。

    于是我们又有下面这个经一步优化的算法。


    3.详谈 Dinic 算法

    之所以详谈Dinic算法,是因为Dinic算法是代码较简单,较容易实现并且效率极高的网络流算法之一,它对于普通的网络图,处理范围可以达到 $ 10^4 - 10^5 $ 。而相比之下,EK算法仅有 $ 10^3 - 10^4 $的处理范围。

    并且某位大师推演出Dinic算法二分图中的复杂度仅有 $m sqrt{n} $ , 所以可以在二分图中愉快地跑网络流 啦。上一道例题P1129 [ZJOI2007]矩阵游戏,熟练后可以切了它。

    我们先介绍一下Dinic算法的核心之一:残量网

    残量网是指在当前网络中的所有节点以及 ** 剩余容量大于 (0) ** 的边构成的子图

    但是有了残量网还不够,要精准判断残量网上两点 (X,Y) 的关系,即判断它们是否有之类神奇的边,我们还要借助满足 $d[y] = d[x] + 1 $ 的分层图

    对于分层图的理解,可以把每一层想象成一个有高度的平面,水流从高的平面流向低的平面(见过多重瀑布吗?)

    Dinic算法思路:

    1 . 开一个队列, 在残量网中BFS一次,按照遍历层数为每个点表上层次 $ d[ x ] $ ,构造分层图并且判断能否从源点 (S) ** 到达汇点 (T)** 。如果失败则说明残量网无法到达汇点。

    2 . 在构造好的残量网上DFS寻找增广路并且,更新反边,(否则DFS无法后悔)

    3 . 不断重复 1 2 步骤直至构造的残量网无法到达汇点

    实践是检验真理的唯一标准

    模板题P3376 【模板】网络最大流

    (AC)代码及讲解:

    #include<bits/stdc++.h>
    using namespace std;
    
    const  int  inf =  0x3f3f3f3 , N = 10000 + 19  ;
    
    int  head[ N ], d[ N ] , to[ N*10*2 ] , w[ N*10*2 ] , next[ N*10*2 ]  ;
    
    int  n , m , s, t , tot = 1 , maxflow = 0 ;//maxflow记录答案 
    
    inline int read()
    {
        int s = 0,w = 1;
        char g = getchar();
        while(g<'0'||g>'9'){if(g=='-')w*=-1;g = getchar();}
        while(g>='0'&&g<='9'){s = s*10+g-'0';g = getchar();}
        return s*w;
    }
    
    queue<int> q ;//广搜时采用队列
    
    void  add( int  x , int  y , int  z ){
    	tot++; to[ tot ] = y , w[ tot ] = z , next[ tot ] = head[ x ] , head[ x ] = tot;
    	tot++; to[ tot ] = x , w[ tot ] = 0 , next[ tot ] = head[ y ] , head[ y ] = tot;
    }//建立边和反向边,注意反向边的流量初始为 0 
    
    bool  bfs(){//在残量网络上构造分层图
    	memset( d , 0 , sizeof(d) ) ; //将之前的分层清0,继续跑残量网络找可行增广路
    	while( q.size() ) q.pop() ; //队列清 0 ;
    	q.push( s ) ; d[ s ] = 1 ; //将源点加入队列,层数为 1 ,开始广搜 
    	while( q.size() ){
    		int  x = q.front() ; q.pop() ;
    		for( int  i = head[ x ] ; i ; i = next[ i ])
    			if( w[ i ] && !d[ to[i] ] ){//目标边的流量不为0 且 为被遍历分层
    				q.push( to[ i ] ) ;
    			    d[ to [ i ] ] = d[ x ] + 1;
    			    if( to[ i ] == t )return 1 ; //找到一条可行增广路
    			}
    	}
    	return 0 ;//未找到,不存在增广路
    }
    
    int  dinic( int  x , int  flow ){ //在分层图上进行增广
    	if( x == t )return flow ;//源点即使汇点,流量不限量
    	int  rest = flow , k ;
    	for( int  i = head[ x ] ; i && rest ; i = next[ i ] )//当前可流入最大流量不为0 ,
            if( w[ i ] && d[ to [ i ] ] == d[ x ] + 1 ){//目标路径有剩余流量,且不存在环之类神奇的东西
            	k = dinic( to[ i ] , min( rest , w[ i ] ) );//继续搜
            	if( !k )d[ to [ i ] ] = 0 ; //剪枝,如果 k(下一层可流入流量图为 0 ),cut
            	w[ i ] -= k ; 
            	w[ i ^ 1 ] += k ;//占用k流量,注意反边要加上k,不然无法退回
            	rest -= k ; //当前节点的剩余汇入流量-k;
            }
           return flow - rest ; //递归完成
    }
    
    int  main()
    {
    	n = read() , m = read() , s = read() , t = read() ;
    	for( int  i = 1 ; i <= m ; ++i ){
    		int  x = read() , y = read() , L = read() ;
    		add( x , y , L ) ;  
    	}
    	int  flow = 0 ; 
    	while( bfs() ){//存在增广路
    		while( flow = dinic ( s , inf ))maxflow += flow ; 
    	}
    	printf("%d",maxflow) ;
    	return  0 ;
    }
    

    Dinic算法一定要手敲一遍板子,不然你连错在哪都查不出来(除了机对)。

    对于无向图,我们在建边的时候要小心,把反边流量赋值! 建双边 !

    附上我的另一篇有关网络流实例讲解的题解 P1129 [ZJOI2007]矩阵游戏


    4.网络流的应用以及ISAP算法引入

    ISAP算法是EK算法的另一类优化, ISAP算法只需一次BFS即可,但代码难度远远高于Dinic算法

    ISAP算法复杂度在非二分图上高于Dinic算法,在二分图则不如Dinic算法

    在此暂时不详讲,日后会更新ISAP算法详解。

    网络流的应用

    网络流应用较广, 但建议新手按以下顺序A题,熟练算法和增强应用能力

    P3376 【模板】网络最大流

    U60438 及川奈砂的蛋糕

    P1129 [ZJOI2007]矩阵游戏

    P2891 [USACO07OPEN]吃饭Dining

    最后是网络流24题

    费用流学习请见:网络流初步详解2

    网络流深入请见这位大佬:网络流深入

    本文到此结束,若有不足,恳请大佬指出。

    如果你喜欢我的文章,请大力点赞支持!感谢。

  • 相关阅读:
    struts2标签具体解释
    证明N={1,2,...,n,...}最高万元 黄晓宁
    Oracle 11g 环境,使用utl_smtp创建一个存储过程来发送邮件
    通过非暴力促进国内操作系统的可行性分析
    pig 的chararry不能用于比较的类型可以comparison operator
    Tair LDB基于Prefixkey找到如何提取一系列性能优化项目key的prefix_size
    hdu 5073 Galaxy(2014acm鞍山亚洲分部 D)
    乐趣与你rabbitMQ 源代码
    我们的空间是它圆——基于Poicare对宇宙的模型
    自己主动旋转木马,自己主动作为幻灯片标题类似或图片
  • 原文地址:https://www.cnblogs.com/ssw02/p/10390214.html
Copyright © 2020-2023  润新知