网络流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);  

 


posted @ 2018-05-05 21:48  光芒万丈小太阳  阅读(285)  评论(0编辑  收藏  举报