最大流小结

最近学习了一发最大流问题,越发沉迷其中。目前来说,最大流算法网络流算法网上有一大堆的讲解,但是确实各种各样都有,对于新手来说的确不是非常友好。不过学习到网络流的有多少还是新手呢= =亦可赛艇。



首先我们需要知道,什么是网络流。

先看一幅图


1代表的是源点,6代表的是汇点,有向图的每条边代表的都是从这条边的源点到这点边的终点的最大容量。

你可以把这幅图想像成是一个排水管道,在保证从源点进入和从汇点流出的水的流量相同的情况下,最大能有多少水流经过这个排水管道。

我们很容易给出一种方案,如下图:


图中标注的8/10表示最大容量为10且当前容量为8

我们很容易看出来这幅图能流过的水流量最大为16

这就是最大流了。不过为了科学,我们还是给出最大流的详细定义:

首先是几个概念:

容量网络(Capacity Network):设G(V,E)是一个有向网络,在V中指定了一个顶点称为源点(记为Vs),以及另一个顶点,称为汇点(记为Vt);对于每一条弧<u,v>∈E,对应有一个权值c(u,v)>0,称为弧的容量(Capacity)。通常把这样的有向网络G称为容量网络。

可行流(Feasible Flow):在容量网络G(V,E)中,满足以下条件的网络流f,称为可行流
    1).弧流量限制条件: 0 <= f(u,v) <= c(u,v), <u,v>∈E
    2).平衡条件:
            ∑f(u,v)-∑f(v,u) = |f|  当u = Vs
            ∑f(u,v)-∑f(v,u) = 0    当u ≠ Vs,Vt
            ∑f(u,v)-∑f(v,u) = -|f| 当u = Vt
        式中:
        ∑f(u,v)表示从顶点u流出的流量总和
        ∑f(v,u)表示流入顶点v的流量总和
        |f|为该可行流的流量,即源点的净流出流量,或汇点的净流入流量

最大流(Maximum Flow):在容量网络G(V,E)中,满足弧流量限制条件和平衡条件、且具有最大流量的可行流,称为网络最大流,简称最大流

好的上面的话看起来非常深奥所以我们直接跳过吧。



那么对于最大流我们应该怎么去求解呢?

我们首先要知道一个定理:

增广路定理:设容量网络G(V,E)的一个可行流为f,f为最大流的充要条件是在容量网络中不存在增广路

根据这个定理,我们有很多的算法来找到最大流,不同的地方在于寻找增广路的方式不同。

先看一段代码:

//通过DFS寻找增广路  
int dfs(int v, int t, int f){  
    if(v == t) return f;//当前位置为终点的话返回f
    used[v] = true;//标记已经访问的节点
    int len = vec[v].size();  
    for(int i = 0; i < len; ++i){  
        edge& e = vec[v][i];
        if(!used[e.to] && e.cap > 0){  
            int d = dfs(e.to, t, min(f, e.cap));  //寻找增广路
            if(d > 0){  
                e.cap -= d;  
                vec[e.to][e.rev].cap += d;//更新反向边
                return d;  
            }  
        }  
    }  
    return 0;  
}  
//求解从s到t的最大流  
int max_flow(int s, int t){  
    int flow = 0;  
    while(true){  
        memset(used, 0, sizeof(used));  
        int f = dfs(s, t, INF);  
        if(f == 0) return flow;  
        flow += f;  
    }  
}
这个代码是Ford-Fulkerson算法的一种实现。

网上对于Ford-Fulkerson算法的讲解有很多,这里就不再叙述了。

另外一种算法是Dinic算法,这个算法的效率更高,所以比较推荐这个算法。同样,给出Dinic算法的代码,不再赘述。

int bfs(){  
    queue<int> q;  
    while(!q.empty()) q.pop();  
    memset(dis, 0, sizeof(dis));  
    q.push(s); dis[s] = 1;  
    while(!q.empty()){  
        int x = q.front(); q.pop();  
        for(int i = head[x]; ~i; i = edge[i].nxt){  
            Edge &e = edge[i];  
            if(e.cap && dis[e.v] == 0){  
                dis[e.v] = dis[x] + 1;  
                q.push(e.v);  
            }  
        }  
    }  
    return dis[t];  
}  
int dfs(int x, int f){  
    if(x == t) return f;  
    int sum = 0;  
    for(int i = head[x]; ~i; i = edge[i].nxt){  
        Edge &e = edge[i];  
        if(e.cap && dis[e.v] == dis[x] + 1){  
            int ret = dfs(e.v, Min(f, e.cap));  
            sum += ret; f -= ret;  
            e.cap -= ret; edge[i^1].cap += ret;  
        }  
    }  
    return sum;  
}  
int dinic(){  
    int ret = 0;  
    while(bfs()) ret += dfs(s, INF);  
    return ret;  
}  
对于Dinic算法讲解个人觉得相对好些的是这篇 《网络流入门—用于最大流的Dinic算法》



那么我们来看几个问题

HDU-3549 Flow Problem 网络流裸题,可用于多种网络流算法的学习

#include <cstdio>
#include <vector>
#include <cstring>
using namespace std;

typedef struct node{
    int to, cap, rev;
    node(int a = 0, int b = 0, int c = 0){
        to = a; cap = b; rev = c;
    }
}EDGE;

const int maxn = 20;
const int INF = 0x3f3f3f3f;

bool vis[maxn];
vector<EDGE> vec[maxn];

void add(int from, int to, int cap){
    vec[from].push_back(node(to, cap, vec[to].size()));
    vec[to].push_back(node(from, 0, vec[from].size() - 1));
}
int dfs(int u, int v, int f){
    if(u == v) return f;
    vis[u] = 1;
    int len = vec[u].size();
    for(int i = 0; i < len; ++i){
        EDGE &e = vec[u][i];
        if(!vis[e.to] && e.cap > 0){
            int d = dfs(e.to, v, min(f, e.cap));
            if(d > 0){
                e.cap -= d;
                vec[e.to][e.rev].cap += d;
                return d;
            }
        }
    }
    return 0;
}
int solve(int s, int n){
    int flow = 0;
    while(true){
        memset(vis, 0, sizeof(vis));
        int d = dfs(s, n, INF);
        if(d == 0) return flow;
        flow += d;
    }
}
int main(){
    int t, cas = 1;
    int a, b, c, n, m;

    scanf("%d", &t);
    while(cas <= t){
        scanf("%d%d", &n, &m);
        for(int i = 0; i <= n; ++i) vec[i].clear();
        for(int i = 0; i < m; ++i){
            scanf("%d%d%d", &a, &b, &c);
            add(a, b, c);
        }
        printf("Case %d: %d\n", cas++, solve(1, n));
    }
    return 0;
}

POJ-1273 Drainage Ditches 网络流基础入门题。

给出N条排水沟的起点和终点及其最大排水量,点1是池塘,点M是小河,让你求出整个排水系统的最大排水量

题解这里有


POJ-1149 PIGS 网络流基础入门题,迈克卖猪模型

有M个猪圈,每个猪圈都有把锁,卖猪的本身是没有钥匙的,现在有N个顾客要来买猪,而且第i个顾客有a[i]把锁的钥匙,能打开k1,k2,k3...kai的猪圈,称这个时候,你可以调整k1,k2,k3...kai号猪圈里面猪的个数。现在已知每个客户要买多少猪,有哪些锁。问你一天最多能卖出去多少猪。

题解这里有


POJ-2112 Optimal Milking 网络流基础入门题,挤奶模型

有k个挤奶器,在牧场里有c头奶牛,每个挤奶器可以满足m个奶牛,奶牛和挤奶器都可以看成是实体,现在给出两个实体之间的距离,如果没有路径相连,则为0,现在问你在所有方案里面,这c头奶牛需要走的最大距离的最小值。

题解这里有

POJ-1459 Power Network 网络流基础入门题,电网模型

一个电网,里面有一些结点,代表电站,消费者,调度站。电站不消耗电能,消费者不产生电能,调度站产生的电能和消耗的电能均为0。现在给出一个电网,有n个结点,np个电站(u)z[表示u号节点是能产生最多z电能的电站],nc个消费者(u)z[表示u是最多消耗z电能的消费者],m条输电线(u,v)z[表示从u到z最多能输送z电能],现在问你最这个电网最多能消费多少电能

题解这里有


AOJ-722 发红包 网络流好题,棒球赛淘汰模型

题解这里有

posted @ 2016-08-09 10:57  _Wilbert  阅读(111)  评论(0)    收藏  举报