zoukankan      html  css  js  c++  java
  • 网络流EdmondsKarp算法模板理解

    先推荐一个讲网络流的博客,我的网络流知识均吸收于此   传送门

    EdmondsKarp算法基本思想:从起点到终点进行bfs,只要存在路,说明存在增广路径,则取这部分路 权值最小的一部分,即为增广路径(也就是这一部分路的最大流量)。然后将这条路上的正向权值都减去min,反向权值都加上min(即,m[i][j]-min,m[j][i]+min,为什么等会再解释)。然后重复此操作,最终就得到了最大流。

    先上模板(也是取自于刚才的博客,真的写的很精简很好懂)。

    邻接矩阵版本。

    #include <queue>  
    #include <cstdio>  
    #include <cstring>  
    #include <iostream>  
    using namespace std;  
    const int MAXN = 300;  
    const int MAX_INT = ((1 << 31) - 1);  
      
    int n;                                      // 图中点的数目  
    int pre[MAXN];                              // 从 s - t 中的一个可行流中, 节点 i 的前序节点为 Pre[i];  
    bool vis[MAXN];                             // 标记一个点是否被访问过  
    int mp[MAXN][MAXN];                         // 记录图信息  
      
    bool bfs(int s, int t){  
        queue <int> que;  
        memset(vis, 0, sizeof(vis));  
        memset(pre, -1, sizeof(pre));  
        pre[s] = s;  
        vis[s] = true;  
        que.push(s);  
        while(!que.empty()){  
            int u = que.front();  
            que.pop();  
            for(int i = 1; i <= n; i++){  
                if(mp[u][i] && !vis[i]){  
                    pre[i] = u;  
                    vis[i] = true;  
                    if(i == t) return true;  
                    que.push(i);  
                }  
            }  
        }  
        return false;  
    }  
      
    int EK(int s, int t){  
        int ans = 0;  
        while(bfs(s, t)){  
            int mi = MAX_INT;  
            for(int i = t; i != s; i = pre[i]){  
                mi = min(mi, mp[pre[i]][i]);  
            }  
            for(int i = t; i != s; i = pre[i]){  
                mp[pre[i]][i] -= mi;  
                mp[i][pre[i]] += mi;  
            }  
            ans += mi;  
        }  
        return ans;  
    }  
     

    这部分代码唯一不好理解的就是,思考了很久,终于在跑步时想通了这部分代码的原理。

    for(int i = t; i != s; i = pre[i]){  
                mp[pre[i]][i] -= mi;  
                mp[i][pre[i]] += mi;  
            } 

    mp[pre[i]][i]-mi好理解,因为已经流过了嘛,那为什么有mp[i][pre[i]]+=mi呢?让我们看一个具体的例子。

                                              

    按照我们的做法,第一次得到了1-2-4-6这条路径,会发现最大流此时是2,然后将路径上的值全部减去2,然后得到的图是这样的:

                                              

    如果只减掉了正向的路径的权值,这个图就不连通了,也就是答案是2,然而其实答案是3,为什么呢,让我们加上反向的权值看一下:

                                              

    红色的就是反向的权值,然后再进行上述过程,会发现还有一条 1-3-4-2-5-6的路径,权值为1,所以答案是2+1=3.(这个答案大家可以根据第一幅图自己想一下,会发现就是3,好神奇有木有?)

          那为什么这么神奇呢,我的理解是,反向路径代表了,这条路曾经通了多少水,比如2-4这条就通2滴水,然后在第二条路径中通过的这1滴水,其实就是2号路径跟1号路径说,你原本在2-4这条路上有两滴水,现在分一滴到我想去的地方,然后我的水去你想去的地方,这样我们都能实现目标。(想一下是不是这么回事)。

          解决了这个难题,那EK算法的模板你就理解啦!

          接下来送上邻接表的版本。

    const int MAXN = 430;  
    const int MAX_INT = (1 << 30);  
      
    struct Edge{  
        int v, nxt, w;  
    };  
      
    struct Node{  
        int v, id;  
    };  
      
    int n, m, ecnt;  
    bool vis[MAXN];  
    int head[MAXN];  
    Node pre[MAXN];  
    Edge edge[MAXN];  
      
    void init(){  
        ecnt = 0;  
        memset(edge, 0, sizeof(edge));  
        memset(head, -1, sizeof(head));  
    }  
      
    void addEdge(int u, int v, int w){  
        edge[ecnt].v = v;  
        edge[ecnt].w = w;  
        edge[ecnt].nxt = head[u];  
        head[u] = ecnt++;  
    }  
      
    bool bfs(int s, int t){  
        queue <int> que;  
        memset(vis, 0, sizeof(vis));  
        memset(pre, -1, sizeof(pre));  
        pre[s].v = s;  
        vis[s] = true;  
        que.push(s);  
        while(!que.empty()){  
            int u = que.front();  
            que.pop();  
            for(int i = head[u]; i + 1; i = edge[i].nxt){  
                int v = edge[i].v;  
                if(!vis[v] && edge[i].w){  
                    pre[v].v = u;  
                    pre[v].id = i;  
                    vis[v] = true;  
                    if(v == t) return true;  
                    que.push(v);  
                }  
            }  
        }  
        return false;  
    }  
      
    int EK(int s, int t){  
        int ans = 0;  
        while(bfs(s, t)){  
            int mi = MAX_INT;  
            for(int i = t; i != s; i = pre[i].v){  
                mi = min(mi, edge[pre[i].id].w);  
            }  
            for(int i = t; i != s; i = pre[i].v){  
                edge[pre[i].id].w -= mi;  
                edge[pre[i].id ^ 1].w += mi;  
            }  
            ans += mi;  
        }  
        return ans;  
    }  
      
    // 加边  
    addEdge(u, v, w);  
    addEdge(v, u, 0);  
    // 调用  
    int ans = EK(s, t);  


  • 相关阅读:
    避免数据脏读
    OGG配置文件中参数化的运用
    GoldenGate基于中间队列文件的初始化
    一次linux中毒,挖矿病毒
    goldengate新版本中查看日志读取点
    dlopen用法参数flag介绍
    gdb调试带参数和调试core
    在现有的git服务器上面创建新的repo
    Play Old Diablo 2 on macOS Catalina
    Odoo中的Environment对象
  • 原文地址:https://www.cnblogs.com/mountaink/p/9536729.html
Copyright © 2011-2022 走看看