• 网络流小结


    网络流小结

    联赛过后,虽然遗憾的没拿到一等,但这不是止步不前的时候,于是我就学了学网络流,学了这几天,也该总结总结了......

    网络流其实就是一种图论模型,在这种图中,边权变成了坑爹的流量,常见的最短路问题也变成了最大流-最小割问题

    网络图和普通图的最大区别也就是边权不再是一个干干巴巴的数字,或者强赋意义的数量,而是真真正正地变成了一个具有实际意义的量—容量.我个人认为,这在现实中有着更广泛的应用,网络流量的分配,水流量的分配等诸多问题,都可以转化为这个模型,网络流具有十分广泛的应用,或许这也是它被称为图论的最高奥义的原因之一吧.

    网络图的存储与普通的图并无太大不同,这也得益于邻接表这一巧妙的设计,我常用的存储方式是这样的:

    struct edge {
    	int to , next , flow ; // 意义与普通邻接表相同,flow就是"边权",也就是容量
    } ;
    

    就是这么一个结构体,当然这其中没有牵扯到费用,因为我还没学习最小费用最大流这类问题

    而只知道如何存储是远远不够的,这只是一个小基础.网络流的第一步就是最大流问题,问题描述为:
    对于一张只具有一个源点和一个汇点的网络,求出从源点到达汇点所能实现的最大流量.
    这个问题与经典图论问题的区别显而易见,这时的边权/容量是一种限制,并且是可变的.而且,显然,对于一条路径的最大流,限制它的是它的最小弧容量.

    一个很显然的思路是每次都尝试去在可行的范围内寻找还未满载的路径并使其达到最大流.这样一条未满载还可增加流量的路径称之为增广路.每次寻找增广路这个思路大的方向上是没有错的,事实上我们也正是这样做的.

    但是,这样有一个很显然的问题,聪明的你一定已经想到了,那就是这会导致加入我们当前得到的可行流中的某一条弧属于一条更优的路径,那么这条更优的路径就会被阻塞而导致无法统计.而如果我们使用一贯的回溯思路来解决,那么复杂度就上升到了指数级,这种复杂度往往难以承受.

    首先明确一个事实,从 (u) 流向 (v) 数值为 (val) 的流量 , 再从 (v) 流向 (u) 数值为 (value) 的流量等价于从 (u) 流向 (v) 的数值为 (val-value) 的流量
    那么我们就需要一种设计,来使得不通过回溯也能完成所有可行流的统计.由此,我们对于一条弧 ((u,v)) 建立它的反向弧 ((v,u)) ,由于一开始并不能有任何的流量从 (v) 流向 (u) 所以一开始,反向弧的所有容量为0.每当我们正向流过 (val) 的流量,对应的反向弧就能反向流动 (val) 的流量.这种机制,正是 (Edmond : Karp) 算法和 (Dinic) 算法的实现基础.反向弧的引入使得我们已经流过的流量有了流回来的机会,因此,它代替了回溯的作用.

    那么一个非常自然的思路呼之欲出了:每次寻找一条增广路进行增广,最后统计出的答案就是最大流!
    增广的过程也十分简单,把增广路径上所有的正向弧容量减去路径上最大的可行流,反向弧同时增大相应的容量即可

    接下来的问题便是寻找增广路,一个很自然的想法是使用 (bfs) 或者 (dfs).而我们也确实是这么做的,只不过一般不使用 (dfs),它很容易变得很慢,于是我们就选用了喜闻乐见的 (bfs) ,稳定的 (O(n)) 复杂度相比较而言十分优秀.

    明确了以上思路之后,就能得到一个正确也不太慢的最大流算法了,这就是(Edmonds : Karp) 算法,简称 (EK) 算法.虽然正确性有了保障,而且也足以应付一些不太刁钻的数据,但它的复杂度是 (O(n imes m^2)) 的,十分的不理想.因此,我们需要一种更加优秀的算法,来保证时间复杂度.

    那我们引入一个新算法姑且称之为(Dinic)算法.(其实就是叫(Dinic)算法)
    首先,我们对图按照 (bfs) 的层次进行分层,然后,我们每次只考虑增广最短路.也就是每次考虑下一层的节点能否增广,并进行增广.然后用一次 (dfs) 走出来这一个阻塞流(阻塞流是指把网络中的弧增广到源点和汇点并不连通.) 然后不断分层并进行增广.
    无费用的最大流使用(Dinic)写出来就这个样子:

    queue < int > q ;
    inline bool bfs () {
        while ( ! q.empty () ) q.pop () ;
        memset ( f , false , sizeof ( f ) ) ;
        for (int i = 1 ; i <= n ; ++ i) last[i] = head[i] ;
        f[s] = 1 ; q.push ( s ) ;
        while ( ! q.empty () ) {
            int j = q.front () ; q.pop () ;
            for (int i = head[j] ; i ; i = e[i].next) {
                int k = e[i].to ;
                if ( ! f[k] && e[i].data ) {
                    f[k] = f[j] + 1 ;
                    q.push ( k ) ;
                }
            }
        }
        return f[node] ;
    }
    
    inline int dfs (int cur , int dist) {
        if ( cur == node ) return dist ;
        for (int & i = last[cur] ; i ; i = e[i].next) {
            int k = e[i].to ;
            if ( f[k] == f[cur] + 1 && e[i].data ) {
                int now = dfs ( k , std::min ( e[i].data , dist ) ) ;
                if ( now != 0 ) {
                    e[i].data -= now ;
                    e[i^1].data += now ;
                    return now ;
                }
            }
        }
        return 0 ;
    }
    
    inline int Dinic () {
        int ans = 0 ;
        while ( bfs () )
            while ( int tmp = dfs ( s , INF ) )
                ans += tmp ;
        return ans ;
    }
    

    然后我还加了一个当前弧优化,然鹅在代码上并没有什么区别.

    最后,介绍一个定理最大流最小割定理:

    最大流最小割定理是网络流理论的重要定理。是指在一个网络流中,能够从源点到达汇点的最大流量等于如果从网络中移除就能够导致网络流中断的边的集合的最小容量和。即在任何网络中,最大流的值等于最小割的容量。 ——以上来自百度百科(Wiki上十分鬼畜所以没用)
    

    其实你只需要记住最大流等于最小割就完了,然后剩下的网络流我就只知道最小费用最大流了,但是我不会,所以不说.
    最后的最后,网络流的题只需要把网络建出来就做出来了,所以最重要的是模型转化.
    常见的套路就是超级源,超级汇,中间连INF.嗯,就是这样!
    (Updated:)
    其实到这里的话,最小费用最大流的算法已经呼之欲出了,那就是把寻找最短路的(bfs)改为其他最短路算法.

    May you return with a young heart after years of fighting.
  • 相关阅读:
    数组的反转和二维数组
    初识数组
    Python学习笔记-Day8
    Python学习笔记-Day7
    Python学习笔记-Day6
    Python学习笔记-Day5
    Python学习笔记-Day4
    Python学习笔记-Day3
    Python学习笔记-Day2
    Python学习笔记-Day1
  • 原文地址:https://www.cnblogs.com/Equinox-Flower/p/10633382.html
Copyright © 2020-2023  润新知