码迷,mamicode.com
首页 > 其他好文 > 详细

网络最大流

时间:2020-02-11 19:21:37      阅读:91      评论:0      收藏:0      [点我收藏+]

标签:代码   网络流   dinic   反向   等于   网络   返回结果   集合   print   

\(\quad\) 网络流(Network-Flows)指的是一张联通无向图 \(G\),每条边具有两个属性:容量、流量。每条边的流量常用 \(f_{i,j}\) 表示,容量常用 \(c_{i,j}\) 表示。网络流有容量限制(Capacity Constraints),一条边的流量不可以超过它的容量;网络流流守恒(Flow Conservation),对于每个除了源点、汇点之外的点,都有:\(f_{in} = f_{out}\),即流入的流量始终等于流出的流量。
\(\quad\) \(\bullet\) 源点(Source Point)
\(\qquad\) 即所有流量的源头。性质:只出不进。
\(\quad\) \(\bullet\) 汇点(Sink Point)
\(\qquad\) 即所有流量的终点。性质:只进不出。
\(\quad\) \(\bullet\) 容量(Capacity)
\(\qquad\) 每条有向边固有的属性。容量指这条边可以容纳的最大流量。
\(\quad\) \(\bullet\) 残量(Residual Capacity)
\(\qquad\) 容量与流量的差。
\(\quad\) \(\bullet\) (Cut)
\(\qquad\) 割又叫割集(Cutset)。在网络流中,割是一些边的集合,满足当整张图去除割集中所有边后不再联通。割所允许通过的最大流量就是割的容量。
\(\quad\) \(\bullet\) 最大流最小割定理(Maxium Flow Minium Cut Theorem)
\(\qquad\) 所有割的容量都比所有可行流的流量大;特别的,容量最小的割的容量等于最大流的流量。如果有一个可行流的流量与一个割的容量相等,那么它们分别为最大流和最小割。
\(\quad\) 如果每条边的流量均不大于它们的容量,则称之为可行流(Feasible Flow),其流量为汇点的流量。特别地,当所有边的流量均为 \(0\) 时,称为零流,其流量为 \(0\)。对于一个可行流,如果存在一条从源点 \(s\) 至汇点 \(t\) 的路径,路径上所有边残量的最小值 \(e > 0\),则这条路径称为一条增广路(Augmenting Path),这条增广路的容量为 \(e\)。当一个可行流不存在增广路时,这个可行流称为最大流(Maxium Flow)。
\(\quad\) 那么,欲求最大流,只需求出一个零流中的所有增广路并增广即可。然而,一条不恰当的增广路可能会阻塞其他的增广路,使得最终的总流量变小。
技术图片
\(\quad\) 如上图,每条边上所标数字代表每条边的残量,初始时为零流,残量等于容量,\(1\) 为源点,\(4\) 为汇点。
技术图片
\(\quad\) 猴子找到了一条增广路:\(1 \to 2 \to 3 \to 4\),流量为 \(1\)。增广后不存在其他增广路,得出最大流为 \(1\)。然而,显然 \(1 \to 2 \to 4,\, 1 \to 3 \to 4\) 才是真正的最大流,流量为 \(2\)
技术图片
\(\quad\) 问题出在了哪儿?我们没有给予程序一个“反悔”的机会。只需要在选择 \(1 \to 2 \to 3 \to 4\) 的同时添加相应的反向边即可。接下来,程序得以找到第二条大小为 \(1\) 的增广路 \(1 \to 3 \to 2 \to 4\),得到正确的最大流 \(2\)。选择 \(3 \to 2\) 这条反向边增广相当于反悔之前选择 \(2 \to 3\) 的操作。
\(\quad\) 这就是 Edmonds-Karp 算法,增广路可以用宽度优先搜索查找。其时间复杂度上限为 \(O(n \cdot m^2)\)

#include <stdio.h>
#include <string.h>

#include <queue>

const int MAXN = 2e2 + 19;

inline int min(const int& a, const int& b){
    return a < b ? a : b;
}

int n, m, s, t, c[MAXN][MAXN], pre[MAXN];

int bfs(){
    int stream = 0x3f3f3f3f; memset(pre, 0, sizeof(pre));
    std::queue<int>q;
    q.push(s);
    while(!q.empty()){
        int node = q.front();
        q.pop();
        if(node == t)
            break;
        for(int i = 1; i <= n; ++i)
            if(!pre[i] && c[node][i]){
                pre[i] = node;
                stream = min(stream, c[node][i]);
                q.push(i);
            }
    }
    pre[s] = 0;
    if(pre[t])
        return stream;
    else
        return 0;
}

int edmonds_karp(void){
    int stream, flow = 0;
    while(stream = bfs()){
        for(int u = t; pre[u]; u = pre[u])
            c[pre[u]][u] -= stream, c[u][pre[u]] += stream;
        flow += stream;
    }
    return flow;
}

int main(){
    scanf("%d%d%d%d", &n, &m, &s, &t);
    for(int i = 1, u, v, w; i <= m; ++i)
        scanf("%d%d%d", &u, &v, &w), c[u][v] += w;
    printf("%d\n", edmonds_karp());
    return 0;
}

\(\quad\) 一般的 EK 算法使用邻接矩阵存图,占用空间太多,效率太低。用链式前向星可以一定程度上优化它。

#include <stdio.h>
#include <string.h>

#include <queue>

const int MAXN = 1e4 + 19, MAXM = 1e5 + 19;

struct Edge{
    int to, next, c;
}edge[MAXM];

int cnt, head[MAXN];

inline void add(int from, int to, int c){
    edge[cnt].to = to;
    edge[cnt].c = c;
    edge[cnt].next = head[from];
    head[from] = cnt++;
}

inline int min(const int& a, const int& b){
    return a < b ? a : b;
}

int n, m, s, t, pre[MAXN];

int bfs(){
    int stream = 0x3f3f3f3f; memset(pre, -1, sizeof(pre));
    std::queue<int>q;
    q.push(s);
    pre[s] = 0;
    while(!q.empty()){
        int node = q.front();
        q.pop();
        if(node == t)
            break;
        for(int i = head[node]; i != -1; i = edge[i].next)
            if(pre[edge[i].to] == -1 && edge[i].c){
                stream = min(stream, edge[i].c);
                pre[edge[i].to] = i;
                q.push(edge[i].to);
            }
    }
    pre[s] = -1;
    if(pre[t] != -1)
        return stream;
    else
        return 0;
}

int edmonds_karp(void){
    int stream = 0, flow = 0;
    while(stream = bfs()){
        for(int u = pre[t]; u != -1; u = pre[edge[u ^ 1].to])
            edge[u].c -= stream, edge[u ^ 1].c += stream;
        flow += stream;
    }
    return flow;
}

int main(){
    memset(head, -1, sizeof(head));
    scanf("%d%d%d%d", &n, &m, &s, &t);
    for(int i = 1, u, v, w; i <= m; ++i)
        scanf("%d%d%d", &u, &v, &w), add(u, v, w), add(v, u, 0);
    printf("%d\n", edmonds_karp());
    return 0;
}

\(\quad\) 慢是 Edmonds-Karp 算法的硬伤。Dinic 算法减少了搜索的次数,一定程度上解决了这个问题。
\(\quad\) Dicnic 算法要求先将整张图分为若干层:\(G_0,G_1,G_2,G_3,...G_k\)。其中,源点在 \(G_0\) 中,与源点最短距离为 \(k\) 的在 \(G_k\) 中。显然,\(\forall G_i,G_j\),若 \(|i - j| > 1\),则 \(\forall x \in G_i,y \in G_j\) 都不存在一条连接 \(x, y\) 的边。这样,使用深度优先搜索寻找增广路,要求每次都进入更深的一层图,即可大大减少搜索次数。
技术图片
\(\quad\)P3376 【模板】网络最大流 中的样例为例。
技术图片
\(\quad\) 为了方便,定义 \(dep_s = 1\)。一次宽度优先搜索即可获得所有点的深度。

int bfs(void){
    memset(dep, 0, sizeof(dep)); dep[s] = 1;
    std::queue<int>q; q.push(s);
    while(!q.empty()){
        int node = q.front();
        for(int i = head[node]; i != -1; i = edge[i].next)
            if(!dep[edge[i].to] && edge[i].capacity)
                dep[edge[i].to] = dep[node] + 1, q.push(edge[i].to);
        q.pop();
    }
    return dep[t];
}

技术图片
\(\quad\) 深搜发现了一条流量为 \(20\) 的增广路,增广并建立反向边,回溯至源点并退出。
技术图片
\(\quad\) 重新标记各点深度(这里没有变化是个巧合),再次深搜,又发现一条 \(20\) 的增广路,建立反向边,回溯至 \(2\)
技术图片
\(\quad\) 搜索下一个深度为 \(3\) 的节点,发现无法到达,回溯至 \(2\)。此时 \(2\) 已无其他出边,回溯至上一个节点,由于是源点,退出。
技术图片
\(\quad\) 重新标记各点深度。如法炮制,找到一条 \(10\) 的增广路。至此已无其他增广路,一路回溯,返回结果 \(50\)
\(\quad\) 按照以上的思路,深搜代码为:

int dfs(int node, int flow){
    if(node == t || !flow)
        return flow;
    int stream = 0, f;
    for(int i = head[node]; i != -1; i = edge[i].next)
        if(dep[edge[i].to] == dep[node] + 1 && (f = dfs(edge[i].to, min(flow, edge[i].capacity)))){
            flow -= f, stream += f;
            edge[i].capacity -= f, edge[i ^ 1].capacity += f;
            if(!flow)
                break;
        }
    return stream;
}

\(\quad\) Dinic 代码为:

int dinic(void){
    int flow = 0;
    while(bfs())
        flow += dfs(s, INF);
    return flow;
}

\(\quad\) 这样的代码已经能够通过 P3376 【模板】网络最大流 了。ISAP 算法将在下一篇博客介绍。

网络最大流

标签:代码   网络流   dinic   反向   等于   网络   返回结果   集合   print   

原文地址:https://www.cnblogs.com/natsuka/p/networkflow_max.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!