菜鸟观点---图论之Dinic最大流算法

菜鸟观点----浅谈图论网络流之Dinic算法

欢迎各位读者关注此ID!

今天的头版先给真正的勇士菜鸟观点---图论之Dinic最大流算法_第1张图片
国家有国家的事情和处理方式,作为我们,能帮助的最多的就是干好自己的本职工作了。

开始正文
Hello大家!我是程序员小田,现在在美国范德堡大学就读计算机专业。这是我的第一篇博客。作为一名在文科大校就读和来自高考大省的cs学生,其实还是蛮心累的。在以前的那种环境里,自己需要通过各种各样的方式和别人拉开差距,以换取自己的心安和更加光明的前途和未来。在这里,也一样,不过并没有以前那么立竿见影,前进的道路充满了迷茫与恐惧。

内心中最深处的恐惧,其实是来自于同行的。我们在我们学校,我们家乡的211,甚至于北大清华,学的专业课知识,归根结底,并无任何不可替代性。也许北大青鸟或者清华同方这种“某某技校”的学生经过半年的系统性培训就可以胜任跟我们能做的所有工作,从标准化的角度上来讲,甚至略强于我们。难道我们和他们差的只有一张学历么?如此,自己还是需要一些属于自己的东西去证明自己。

计算机本身就是建立在竞赛上的学科,通过一个机缘巧合被队友拉入了ACM/ICPC的深坑,才发现原来有这么多优秀的同行,这么多有意思的知识和伟大的思想,而我竟对他们一无所知。我引以为傲的,使别人高中就学过的知识,属实惭愧。

大一上学期被同学拉去打了一场ACM,我们三只对于编程一知半解的大一新生代菜鸟只写出来三道题,其中队友写出来了2道半,自己懵逼之余仅仅贡献了半道思路上的助攻,最后是只拿了学校第九名。我们约好好好学习,来年再战。

作为信息学奥赛的延伸,我们比别人差的可能是不仅仅是三到六年的准备时间。而一名ACM的参赛选手的生命周期大概只有两年,用两年去对抗别人的8年的确有点困难,但并非不可能呀hhh。我们有信心去改变,至少,如果不知道这些,我的状态也不会比现在好。那就加油准备吧。

其实,备战还是有难度的,我们缺乏教练的引领,可靠的资源,和编程的硬底子。我们学校成建制的队伍还只有一个去年战绩甚至于不如我们几个大一新生的ACM for Womenhhhh。暑假,在队友的带领下,我们从刘汝佳的紫书开始刷起。说起这本书,我真的又爱又恨,的确给我们科普了很多知识,但是语言emmm,对于新手的确不太友好,很多思想在大学里都是在算法课(Algorithm)教,但这个课可是大三才上的啊啊啊啊啊啊。

后来,了解到北大暑假正好开设ICPC的相关课程,就屁颠屁颠跑去了。的确,中国的最高学府果然是不同凡响,教我们的是北大信息科学院的郭炜老师,人送外号郭神。课其实挺难的,但比刘汝佳的三件套来讲还是非常不错的,最重要的是,郭神的讲课水平不是一般高,一天四个小时的课程是我在学校两三周的量,全程没有一句废话,全都在倒干货,每次上完课整个人都有一个小时连话都说不出来。通过郭神通俗易懂的语言,自己将那些从前根本不敢想的算法了解了个大概,最后的考试嘛,总之肯定及格了就是了。不过14天,我的作业只完成了八分之一(竟然就进历史提交的前100了hhhh),所以很多东西还是没弄清。图论算法仍然是满头雾水,好在郭神的ppt资料是不会过期的,于是乎自己打算课后仔细研究下,不能辜负了他的一片好意。
菜鸟观点---图论之Dinic最大流算法_第2张图片
我这个人有个习惯,就是在写代码的时候会疯狂注释,所以我打算把这个blog作为一个个人账号带大家,更多的是自己,去通过菜鸡的眼光和理解对算法进行理解。如果各位和我一样对一些csdn博主的刘汝佳代码仓库搬运工兼复读机和“A肯定不选B直接排除C送分选项所以选D”头疼的话,欢迎来看看我的。言归正传,我个人是习惯先看郭神代码,然后再把要点写下来记在纸上,最后通过自己的纸质资料进行复原算法代码本身。以下的代码均来自于北京大学郭炜老师,如果涉及侵权请联系我本人删除代码。

网络流最大最小流–Dinic算法

最大最小流,这个是我们去年北美qualifier被卡脖子的内容,因为当时根本不理解图论,我们学校也没有系统性讲解,所以当时一道简单的题就白白浪费了我们好几个小时的时间。

最大最小流,这个顾名思义就是一个系统中有若干条管道,每个管道都有单位时间最大流量,求单位时间系统从源点到汇点最大流量。这种题的思路就是木桶理论,即能承载的流量为流量最小的一根或者多根管道(短板),但是往往最难确定的也就是这样的管道通常由不止一根,那么我们如何判断呢?来看看POJ上Drainage Ditches的例子,这道是一个很典型的最大流的白板题。

首先,Dinic算法是借用广搜的思想,将管道结点分层,因为水单位时间内只能流一处地点,从一层流出的最小流量就是系统的单位时间最小流量,那么如何去实现呢?
看代码吧,我把该写的东西都放在注释里了。其中的一个要点就是如果找到汇点就代表了找到了一条最小流量,就从最小的那一层开始,添加反向边,取消最小的流量,代表找过了。如果没有碰到汇点(死胡同),而已然可以分层(残余网络又流量),那就代表了这个图还有一些流量可以流出去但咱们还没把这部分计算在内,这时候就回退到可以分层并且可以走得通的点进行计算。

#include 
using namespace std;
#define limit 200 + 5
#define INF 0x3f3f3f3f
void read(int &x){
    char ch = getchar();x = 0;
    for (; ch < '0' || ch > '9'; ch = getchar());
    for (; ch >='0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
}//读输入
/***
 *图论算法之最大最小流dinic算法在POJ1273上的运用,ACM冲鸭!!!!!
 */
int G[limit][limit];
int s,e,f;//分别表示起点,终点和流量
int visited[limit], level[limit];
bool countLayer(){
    int layer = 0;
    dequeq;//当做栈使用的q
    memset(level, 0xff , sizeof(level));//初始化为-1表示没有遍历过
    level[1] = 0;//表示初始分层
    q.push_back(1);//推入栈中以开始编号
    while(!q.empty()){
        int vertices = q.front();//栈的性质是LIFO,在此不在多解释,vertices代表是发展的节点
        q.pop_front();//相当于stack的pop方法
        for(int i = 1 ; i <= e; ++i){
            if(G[vertices][i] > 0 && level[i] == -1){
                //代表如果这点和下一点之间有流量而且没有被分过层
                level[i] = level[vertices] + 1;//分层,从开始开发的点到上一个节点的距离层数 + 1
                if(i == e){
                    //i == e 代表分到了汇点,所以直接完成整个步骤
                    return true;
                }else{
                    q.push_back(i);//继续探索下一个节点
                }
            }
        }
    }
    return false;
}
int dinic(){
    //注入灵魂
    int i, start, maxFlow = 0;//i在此处需要用作判断是否已经到达汇点,所以不能在for循环里面定义
    //emm别问我为什么要定义start这个无用变量,郭神是这样说的,这样写的,所以为了尊重原作还是定义一下吧
    dequeq;//定义一个双向队列当成栈使用,什么?这里其实是利用了deque是动态数组实现,有[]这个运算符可以在O(1)时间内访问任意一个元素
    //同时又具有栈的性质,即后进先出,所以在此处选用双向队列
    while(countLayer()){//只要还能分层,就继续这个循环
        q.push_back(1);//原作上郭神是用了1-based 下标,我个人喜欢用0-based下标,但在icpc中这样有诸多不便,所以还是视情况而定
        memset(visited , 0, sizeof(visited));//别问,问就是没来过
        visited[1] = 1;//将原点标为visited,这一步骤和dfs还有bfs类似
        while(!q.empty()){
            int n = q.back();//这一步为什么是从后面取点等下会讲
            if(n == e){
                //e为代表汇点的全局变量,所以此处为碰到汇点了怎么办
                int minC = 999999;//设为无穷大方便比较出最小值,INF最好定义成0x3f3f3f3f而不是0x7fffff
                int minV;//minV记录最小流出现的节点
                for(i = 1 ; i < q.size(); i++){
                    int vs = q[i - 1];//记录起始点
                    int ve = q[i];//记录终点
                    if(G[vs][ve] > 0){
                        if(minC > G[vs][ve]){
                            minC = G[vs][ve];//如果该增光路径仍然有剩余流量,而且比minC小,那么记住它
                            minV = vs;//记录最小流节点出处
                        }
                    }
                }
                maxFlow += minC;//单位最大流为各个增广路径的最小流(卡着脖子),所以此处要加上
                for(i = 1 ; i < q.size();++i){
                    int vs = q[i - 1];//记录起始点
                    int ve = q[i];//记录终点
                    G[vs][ve] -= minC;
                    G[ve][vs] += minC;//添加反向边,表示此处流量已经被计算过无需重复计算
                }
                while(!q.empty() && q.back() != minV){
                    visited[q.back()] = 0;//回溯,因为要从上一个最小流节点继续探索,所以需要恢复这之前的一切
                    q.pop_back();
                }
            }else{
                for(i = 1 ; i <= e ; ++i){
                    if(G[n][i] > 0 && level[i] == level[n] + 1 && !visited[i]){//这一点需要符合三个条件
                        //第一是这一条边仍然有剩余流量,再就是这一点是选定开发节点的下一层,并且没有被访问过
                        visited[i] = 1;//表示访问过了
                        q.push_back(i);//把这一点当做下一次dfs的对象
                        break;//找到就歇菜了,如果不及时停止的话会出现大量重复访问的情况拉低效率
                    }

                }
                if(i > e){
                    q.pop_back();//如果i == e了就代表在这之前也没有找到一个合适的点,意味着下一次是死胡同,
                    //所以进行退栈回溯处理
                }
            }
        }
    }
    return maxFlow;;//嘿嘿终于找到了
}
int main(){
    while(scanf("%d%d", &s, &e) == 2){
        memset(G,0, sizeof(G));//初始化数组存边
        int vs, ve ,flow;
        for(int i = 0 ; i < s; ++i){
            scanf("%d%d%d" , &vs, &ve,&flow);
            G[vs][ve] += flow;//因为可能存在共同边,所以在此直接合并为一条边避免麻烦
        }
        printf("%d\n", dinic());//大功告成啦!hhhhh
    }
    return 0;
}

大概就是这么个道理,我拿这个算法过掉了2017American Qualifier的gcd网络流的题,很好用的。

顺带说一句,Dinic算法的时间复杂度官方注释是O(E * E * V),也就是介于平方和立方之间,但是实际上的表现几乎是O(N)的,poj这道用Dinic算法比EK算法快得不止一点点,只有2ms,所以dinic一定要搞懂。

大家别再问我为啥定义start这个变量了哈哈哈,郭神这么做了,我尊重原作就加上了hhh。

最后,快回学校了,我争取这几天继续攻克Dijkstra,BF,SPFA算法吧,希望能看懂吧。最后祝大家今年的Qualifier/省赛夺金!!!
谢谢大家!
----tiany7

你可能感兴趣的:(ICPC,ICPC,ACM,算法,计算机)