• 学习笔记 --- 最大流Dinic算法


    为与机房各位神犇同步,学习下网络流,百度一下发现竟然那么多做法,最后在两种算法中抉择,分别是Dinic和ISAP算法,问过
    CA爷后得知其实效率上无异,所以决定跟随Charge的步伐学习Dinic,所以来写点心得
    

    网络流(最大流)的做法可以进行浅显的理解:
    一张图可以认为是一个排水管道,每个点为管道的交叉点,每个边的边权即是这条管道的水的容量,给定一个源点和一个汇点,源点有∞的水量供给,问汇点最大可以获得多少水,所求即为最大流
    但是有点题目不一定会给定源点或者汇点,还是因题而异,而且还有很多题目需要进行拆点建图也是复杂的不行。。。(沙茶的我还需要多练多想啊)

    Dinic算法:
    大体上是这么一个流程:
    1.利用BFS分层,即按照距离源点的最短距离来分出层次
    2.在分层图上DFS增广,找出这一次的最大流量,然后累入总ans中,值得注意的是,这一阶段中没找到一个当前值now,需要将所有正向边权-now,并把其反向边权+now,这可以使得以后的增广中可以后悔,即可以把之前走错的路再走一遍,保证答案的正确性
    3.不断对残余网络进行BFS,直到无法到达源点结束,每次BFS后多次DFS求值直到找不到新的路径
    一个很高端详细的讲解BLOG:https://comzyh.com/blog/archives/568/

    大体的框架如下:
    一个最基本的模板(next数组实现)

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    struct data{
        int next,to,v;
    }edge[500]={0};
    int cnt=1,head[500]={0};
    int q[2000],h,t;
    int dis[500]={0};
    int n,m,ans;
    
    void add(int u,int v,int w)
    {
        cnt++;
        edge[cnt].next=head[u];
        head[u]=cnt;
        edge[cnt].to=v;
        edge[cnt].v=w;
    }
    
    bool bfs()
    {
        memset(dis,-1,sizeof(dis));
        q[1]=1;dis[1]=0;
        h=0;t=1;
        while (h<t)
            {
                int j=q[++h],i=head[j];
                while (i)//枚举所有与当前节点相连的 
                    {
                        if (dis[edge[i].to]<0 && edge[i].v>0)//判断是否已经分层过&&是否能流(联通) 
                            {
                                dis[edge[i].to]=dis[j]+1;
                                q[++t]=edge[i].to;
                            }
                        i=edge[i].next;
                    }
            }
        if (dis[n]>0)//如果能够到达汇点,则可以继续,否则就不能继续 
            return true;
        else
            return false;
    }
    
    int dfs(int loc,int low)
    {
        int ans=0;
        if (loc==n) return low;
        int i=head[loc];
        while (i)//枚举所有与当前点相连的 
            {
                //判断 是否能流(联通) && 它下面那条个点与它是否层数+1(找路径的要求必须找下一层的点) && 当前ans是否大于0 
                if (edge[i].v>0 && dis[edge[i].to]==dis[loc]+1 && (ans=dfs(edge[i].to,min(low,edge[i].v))))
                    {
                        edge[i].v-=ans;//正向边-去当前值 
                        edge[i^1].v+=ans;//反向边+上当前值 ,^1异或即反向边的位置 i为单数为i-1,为双数为i+1 
                        return ans;
                    }
                i=edge[i].next;
            }
        return 0;
    }
    
    int main()
    {
        scanf("%d%d",&m,&n);
        for (int i=1; i<=m; i++)
            {
                int s,e,v;
                scanf("%d%d%d",&s,&e,&v);
                add(s,e,v);
                add(e,s,0);//因为需要反向边在建完正向边后立即建一个反向边,初值为0 
            }
        int ans=0;
        while (bfs())
            {
                int now;
                while ((now=dfs(1,0x7fffffff)))
                    ans+=now;
            }//Dinic 
        printf("%d",ans);
        return 0;
    } 

    还有很多不同的规则有不同的模板,以后会更。

    2016.1.3 补。
    edge【】的范围一定要计算好,好几次都是这个地方开小了,这种错误太低级了。。
    建图的过程需要发散一下思维,不能仅仅局限于超级源与超级汇,很多题目的需要建一些次级的点,或者进行拆点,抑或是二分后多次建图多次判定,千变万化。
    在建边的时候需要建一条反向边(一般反向初始都是0,为后续反向弧用),当然 有的时候题目中需要开始就建双向边。。要好好把握

    2016.1.17补。
    当前弧优化(理论上常数级优化实际上优化效果完美):

    bool bfs()
    {
        memset(dis,-1,sizeof(dis));
        q[1]=0; dis[0]=1;
        h=0;t=1;
        while (h<t)  
            {
                int j=q[++h],i=head[j];
                while (i)
                    {
                        if (edge[i].v>0 && dis[edge[i].to]<0)
                            {
                                dis[edge[i].to]=dis[j]+1;
                                q[++t]=edge[i].to;
                            }
                        i=edge[i].next;
                    }
            }
        if (dis[num]>0)
            return true;
        else
            return false;
    }
    
    long long dfs(int loc,long long low)
    {
        if(loc==num)return low;
        long long flow,cost=0;
        for(int i=cur[loc];i;i=edge[i].next)
            if(dis[edge[i].to]==dis[loc]+1)
                {
                    flow=dfs(edge[i].to,min(low-cost,edge[i].v));
                    edge[i].v-=flow;edge[i^1].v+=flow;
                    if(edge[i].v) cur[loc]=i;
                    cost+=flow;if(cost==low)return low;
                }
        if(!cost)dis[loc]=-1;
        return cost;
    }
    
    long long dinic()
    {
        long long temp=0;
        while (bfs())
            {
               for (int i=0; i<=num; i++) cur[i]=head[i];
               temp+=dfs(0,maxl);
            }
        return temp;
    }

    多路增广+炸点+当前弧优化

  • 相关阅读:
    【STL】各容器成员对比表
    windows笔记页文件支持的内存映射文件
    windows笔记【内核对象线程同步】线程同步对象速查表
    windows笔记虚拟内存
    windows笔记使用内存映射文件在进程之间共享数据
    svn个人服务端
    解决安装Visual Studio .NET 2003 时FrontPage 2000 WEB 扩展客户端 安装失败
    vc6.0转vs2008连接错误
    Sublime Text插件推荐
    末日了,说出你的梦想、愿望还有遗憾吧。
  • 原文地址:https://www.cnblogs.com/DaD3zZ-Beyonder/p/5346239.html
Copyright © 2020-2023  润新知