zoukankan      html  css  js  c++  java
  • 网络流概念+EdmondKarp算法+Dinic(Dinitz)

    网络流问题在实际解决题目中有很多用处

    有时可与二分图匹配相搭配(然而我还没有学二分图匹配2333)

    所以我还是决定学一下网络流

    于是便有了这篇博客

    废话不多说

    先介绍下概念

    关于网络流的基本概念

    图,边,点

    设边(Edge)的集合为E

    点(vertex)的集合为V
    

    图为G

    则G=(V,E)

    (color{pink}{S指源点(起点),T指汇点(终点)})

    (color{pink}{注意,这里E和V是集合,而G是一个二元组})

    (color{pink}{二元组:数据结构的一种(不是指信息的,也许信息也有 雾),二元组(D,R),D为数据元素的有限集,R为D的关系之间的有限集})

    (color{pink}{明显这里D指V,R指E})

    容量网络

    G(V,E)只不过要求

    • 有向

    • 相邻两点间的最大边大于0(c(u,v)>0)

    则称G为容量网络

    弧的流量

    话说 我不懂为什么都叫它弧不叫边的2333

    即实际流量用f(u,v)表示

    网络流

    即集合 f = { u(in)V,v(in)V-{u}|f(u, v) }

    可行流

    • 0 ≤ f(u,v) ≤ c(u,v)

    • 平衡条件除源点S和汇点T外流入流量总和=流出流量总和

    增广路

    即一条从S到T的路径且路径上每条边残留容量都为正(c(u,v)-f(u,v)>0)

    增广

    将S到T的一条增广路拿出来,设其中的最小边权(残留容量)为minl

    将该路途上的每边 -= minl

    将minl累加至ans中

    以上 为前置概念

    算法

    题目

    接下来的题目以P3376 【模板】网络最大流为例子

    EdmondKarp

    该算法时间复杂度O(V(E^2))

    思路不断找增广路进行增广,当找不到增广路时,即此时的ans就为网络最大流

    找增广路

    inline bool Bfs()
    {
        bool vis[MAXN] = {};
        memset(pre,0,sizeof(pre));
        queue<int> q;
        q.push(s);
        vis[s] = 1;
        while(!q.empty())
        {
            int u = q.front();
            if(u == t) return 1;
            q.pop();
            for(int i = fire[u];i;i = Edge[i].next)
            {
                int v = Edge[i].v;
                if(!vis[v]&&Edge[i].w)
                {
                    vis[v] = 1;
                    pre[v].v = u;
                    pre[v].Edge = i;
                    //pre用来记录路径
                    if(v == t) return 1;
                    q.push(v);
                }
            }
        }
        return 0;
    }
    
    

    EdmondKarp

    inline int EdmondKarp()
    {
        int ans = 0;
        while(Bfs())
        {
            int i,minl = inf;
            for(i = t;i != s;i = pre[i].v)
                 minl = min(minl,Edge[pre[i].Edge].w);
            //找最小值
            for(i = t;i != s;i = pre[i].v)
            {
                Edge[pre[i].Edge].w -= minl;
                //Edge[pre[i].Edge ^ 1].w += minl;
            }
            //将该路途上的每边 -= minl
            ans += minl;
            //将minl累加至ans中
        }
        return ans;
    }
    

    然后这就是大概思路

    (color{pink}{但是})

    这样会错

    假设上面每条边容量为1

    如果找到的第一条增广路为1-2-3-4

    那么

    再也找不到增广路

    此时ans 为 1

    但显然ans为2

    (color{pink}{WA})

    原因很显然这样随便走很明显不对啊 又没什么定理保证正确

    不过 EdmondKarp还有一点

    我们要给程序一个(color{pink}{反悔})的机会

    口技·习传

    不玩梗了

    建双向边, 将该路途上的每边 -= minl的时候同时将该路途上的每边的反边 += minl

    即加上上面被我注释掉的代码

    建双向边后

    蓝边权值1,红边权值0,绿边权值1

    第二次就可以找到1-3-2-4了

    我们用的是链式前向星

    为方便我们让一条边与她的反边相邻

    即 使 边id^1=反边id且反边id^1=边id
    
    即 min(边id,反边id) + 1 = max(边id,反边id)
    

    所以第一条边id必须为偶数

    否则如果第一条边从奇数开始 她的

    反边id=边id+1!=边id^1
    

    代码

    #include <queue>
    #include <cstdio>
    #include <climits>
    #include <cstring>
    #include <iostream>
    using namespace std;
    
    const int MAXM = 200010,MAXN = 10010,inf = INT_MAX;
    struct Node
    {
        int v,w,next;
        Node(){v = w = next=0;}
    }Edge[MAXM];
    struct Node2
    {
        int v,Edge;
    }pre[MAXN];
    int cnt,fire[MAXN],s,t;
    inline void AddEdge(int u,int v,int w)
    {
        Edge[++cnt].v = v;
        Edge[cnt].w = w;
        Edge[cnt].next = fire[u];
        fire[u] = cnt;
    }
    inline void Build(int u,int v,int w)
    {
        AddEdge(u,v,w);
        AddEdge(v,u,0);
        //反边权值为0
    }
    inline bool Bfs()
    {
        bool vis[MAXN] = {};
        memset(pre,0,sizeof(pre));
        queue<int> q;
        q.push(s);
        vis[s] = 1;
        while(!q.empty())
        {
            int u = q.front();
            if(u == t) return 1;
            q.pop();
            for(int i = fire[u];i;i = Edge[i].next)
            {
                int v = Edge[i].v;
                if(!vis[v]&&Edge[i].w)
                {
                    vis[v] = 1;
                    pre[v].v = u;
                    pre[v].Edge = i;
                    if(v == t) return 1;
                    q.push(v);
                }
            }
        }
        return 0;
    }
    inline int EdmondKarp()
    {
        int ans = 0;
        while(Bfs())
        {
            int i,minl = inf;
            for(i = t;i != s;i = pre[i].v)
                 minl = min(minl,Edge[pre[i].Edge].w);
            for(i = t;i != s;i = pre[i].v)
            {
                Edge[pre[i].Edge].w -= minl;
                Edge[pre[i].Edge ^ 1].w += minl;
            }
            ans += minl;
        }
        return ans;
    }
    int main()
    {
        cnt = -1;
        这里设为-1,但Build里+了1
        所以实际上第一条边从零(偶数)开始的(~~魔法书~~/~~异世界生活~~)
        int i,n,m;
        scanf("%d%d%d%d",&n,&m,&s,&t);
        for(i = 1;i <= m;i++)
        {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            Build(u,v,w);
        }
        int ans = EdmondKarp();
        printf("%d",ans);
        return 0;
    }
    
    

    这样就可以AC这道题P3376 【模板】网络最大流

    (color{pink}{但是})

    这样的代码还有bug 所以我说洛谷数据水啊

    
    我的链式前向星是这样写的
    
    明显条件是当i等于0时退出
    
    但 之前cnt初值为-1
    
    第一条边id为0
    
    所以当i等于0的时候也可能指的第一条边
    
    订正 将cnt改为 1
    for(int i = fire[u];i;i = Edge[i].next)
            {
                int v = Edge[i].v;
                if(!vis[v]&&Edge[i].w)
                {
                    vis[v] = 1;
                    pre[v].v = u;
                    pre[v].Edge = i;
                    if(v == t) return 1;
                    q.push(v);
                }
            }
    
    #include <queue>
    #include <cstdio>
    #include <climits>
    #include <cstring>
    #include <iostream>
    using namespace std;
    
    const int MAXM = 200010,MAXN = 10010,inf = INT_MAX;
    struct Node
    {
        int v,w,next;
        Node(){v = w = next=0;}
    }Edge[MAXM];
    struct Node2
    {
        int v,Edge;
    }pre[MAXN];
    int cnt,fire[MAXN],s,t;
    inline void AddEdge(int u,int v,int w)
    {
        Edge[++cnt].v = v;
        Edge[cnt].w = w;
        Edge[cnt].next = fire[u];
        fire[u] = cnt;
    }
    inline void Build(int u,int v,int w)
    {
        AddEdge(u,v,w);
        AddEdge(v,u,0);
    }
    inline bool Bfs()
    {
        bool vis[MAXN] = {};
        memset(pre,0,sizeof(pre));
        queue<int> q;
        q.push(s);
        vis[s] = 1;
        while(!q.empty())
        {
            int u = q.front();
            if(u == t) return 1;
            q.pop();
            for(int i = fire[u];i;i = Edge[i].next)
            {
                int v = Edge[i].v;
                if(!vis[v]&&Edge[i].w)
                {
                    vis[v] = 1;
                    pre[v].v = u;
                    pre[v].Edge = i;
                    if(v == t) return 1;
                    q.push(v);
                }
            }
        }
        return 0;
    }
    inline int EdmondKarp()
    {
        int ans = 0;
        while(Bfs())
        {
            int i,minl = inf;
            for(i = t;i != s;i = pre[i].v)
                 minl = min(minl,Edge[pre[i].Edge].w);
            for(i = t;i != s;i = pre[i].v)
            {
                Edge[pre[i].Edge].w -= minl;
                Edge[pre[i].Edge ^ 1].w += minl;
            }
            ans += minl;
        }
        return ans;
    }
    int main()
    {
        cnt = 1;
        int i,n,m;
        scanf("%d%d%d%d",&n,&m,&s,&t);
        for(i = 1;i <= m;i++)
        {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            Build(u,v,w);
        }
        int ans = EdmondKarp();
        printf("%d",ans);
        return 0;
    }
    

    这样才没问题T_T

    Dinic(Dinitz)

    作者叫Dinitz

    后来某大佬(雾)教课时念成了Dinic

    于是大家都叫这个算法为Dinic算法了2333

    这个算法大概就是EdmondKarp的升级版

    理论上快很多 但最慢会退化为(O(VE^2))

    思路就是 考虑到EdmondKarp每次都重新一遍Bfs找增广路

    我们能否在一次或几次搜索中找到很多条增广路呢?

    这就是Dinic算法(连我也这样叫 恩 作者的真名不用管了)的核心处理之处了

    具体实现

    引入一个层次网络的概念

    其实就是给予(forall) v (in)V 一个值 (depth_i)

    记录从源点到她最少需要经过几条还活着的边(边权为0的已经死了)

    这个操作一遍Bfs()搞定

    然后找增广路就用Dfs()来找

    (Bfs)

    inline bool Bfs()
    {
        memset(depth,-1,sizeof(depth));
        queue<int> q;
        q.push(s);
        depth[s] = 1;
        while(!q.empty())
        {
            int u = q.front();
            q.pop();
            for(int i = fire[u];i;i = Edge[i].next)
            {
                int v = Edge[i].v;
                if(depth[v] == -1&&Edge[i].w)
                {
                    depth[v] = depth[u] + 1;
                    q.push(v);
                }
            }
        }
        return (depth[t] != -1);
    }
    
    

    Dfs

    inline int Dfs(int u,int minl)
    {
        if(u == t) return minl;
        int i,delta1 = 0;
        for(i = head[u];i;i = Edge[i].next)
        {
            int v = Edge[i].v,w = Edge[i].w;
            head[u] = i;
            不断更新head[u]
            直接用fire[u]也没错 但在同一次Dfs已经走过的最后的一条边失去价值
            减少时间复杂度
            if(depth[v] == depth[u] + 1&&w)
            {
                int delta = Dfs(v,min(minl,w));
                minl -= delta;
                delta1 += delta;
                Edge[i].w -= delta;
                Edge[i ^ 1].w += delta;
                if(!minl) break;
            }
        }
        if(!delta1) depth[u] = -1;
        return delta1;
    }
    

    Dinic(Dinitz)

    inline int Dinic()
    {
        int ans = 0;
        while(Bfs())
        {
            for(int i = 1;i <= n;i++) head[i] = fire[i];
            因为head在Dfs中进行了更改,但这对另外一次Dfs不造成影响
            因为另外一次Dfs前由再进行了一次Bfs改变了depth
            所以这里得把值赋回去
            ans += Dfs(s,INT_MAX);
        }
        return ans;
    }
    

    完整代码

    #include <queue>
    #include <cstdio>
    #include <climits>
    #include <cstring>
    #include <iostream>
    using namespace std;
    
    const int MAXM = 200010,MAXN = 10010,inf = INT_MAX;
    struct Node
    {
        int v,w,next;
        Node(){v = w = next=0;}
    }Edge[MAXM];
    int cnt,fire[MAXN],head[MAXN],s,t,depth[MAXN],n;
    inline void AddEdge(int u,int v,int w)
    {
        Edge[++cnt].v = v;
        Edge[cnt].w = w;
        Edge[cnt].next = fire[u];
        fire[u] = cnt;
    }
    inline void Build(int u,int v,int w)
    {
        AddEdge(u,v,w);
        AddEdge(v,u,0);
    }
    inline bool Bfs()
    {
        memset(depth,-1,sizeof(depth));
        queue<int> q;
        q.push(s);
        depth[s] = 1;
        while(!q.empty())
        {
            int u = q.front();
            q.pop();
            //printf("%d:",u);
            for(int i = fire[u];i;i = Edge[i].next)
            {
                int v = Edge[i].v;
                //printf("[%d %d %d] ",u,i,v);
                if(depth[v] == -1&&Edge[i].w)
                {
                    depth[v] = depth[u] + 1;
                    q.push(v);
                }
            }
            //putchar('
    ');
        }
        return (depth[t] != -1);
    }
    inline int Dfs(int u,int minl)
    {
        if(u == t) return minl;
        int i,delta1 = 0;
        for(i = head[u];i;i = Edge[i].next)
        {
            int v = Edge[i].v,w = Edge[i].w;
            head[u] = i;
            if(depth[v] == depth[u] + 1&&w)
            {
                int delta = Dfs(v,min(minl,w));
                minl -= delta;
                delta1 += delta;
                Edge[i].w -= delta;
                Edge[i ^ 1].w += delta;
                if(!minl) break;
            }
        }
        if(!delta1) depth[u] = -1;
        return delta1;
    }
    inline int Dinic()
    {
        int ans = 0;
        while(Bfs())
        { 
            for(int i = 1;i <= n;i++) head[i] = fire[i];
            ans += Dfs(s,INT_MAX);
        }
        return ans;
    }
    int main()
    {
        cnt = 1;
        int i,m;
        scanf("%d%d%d%d",&n,&m,&s,&t);
        for(i = 1;i <= m;i++)
        {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            Build(u,v,w);
        }
        int ans = Dinic();
        printf("%d",ans);
        return 0;
    }
    
    
  • 相关阅读:
    C++ error C2678: 二进制“+”: 没有找到接受“const char [22]”类型的左操作数的运算符(或没有可接受的转换)没有与这些操作数匹配的“+”运算符...
    Qt 没有byte
    C++ 解决:错误 1 error MSB8031: Building an MFC project for a non-Unicode character set is deprecated
    .NET Core-插件如何读取指定配置结点
    CodeSmith4-案例
    CodeSmith3-对象(CodeTemplate、Response、CodeTemplateInfo)
    CodeSmith2-Property(属性)
    vscode
    OPCUA suscription TimeoutError
    OPCUA 节点层级
  • 原文地址:https://www.cnblogs.com/resftlmuttmotw/p/11420924.html
Copyright © 2011-2022 走看看