网络流(二) 最大流算法的实现

嗯接着前篇…有了前面的原理铺垫,接下来的可能会简单一些…?
FF看见烦,SAP不想学。
所以只剩下EK和Dinic了。
模板题:luogu3376


Edmond-Karp算法

基本上上把EK的queue改成stack就成了FF,所以这里不讨论FF的问题。
先贴代码,然后再说一说其含义。

#include <bits/stdc++.h>
using namespace std;
#define MAXN 100003
struct node{
    int fr, to, va; //从fr到to有一条权值为v的边
    int nxt;
}edge[MAXN];
int cnt, head[MAXN];
inline void read(int &x) {
    x = 0; char c = getchar();
    while(!isdigit(c)) c = getchar();
    while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
} 
inline void add_edge(int u, int v, int w) { //邻接表存边
    edge[cnt].fr = u, edge[cnt].to = v, edge[cnt].va = w;
    edge[cnt].nxt = head[u], head[u] = cnt++;
    edge[cnt].fr = v, edge[cnt].to = u, edge[cnt].va = 0; 
    edge[cnt].nxt = head[v], head[v] = cnt++; 
} 
int pre[MAXN];
bool vis[MAXN];
int st, ed, n, m;
bool BFS (int S, int T) {
    queue<int> q;
    memset(vis, 0, sizeof(vis));
    memset(pre, -1, sizeof(pre));
    q.push(S);  vis[S] = 1;
    while(!q.empty()) {
        int u = q.front();
        q.pop();
        for(int i = head[u]; i != -1; i = edge[i].nxt) {
            int tmp = edge[i].to;
            if(edge[i].va <= 0 || vis[tmp]) continue;
            pre[tmp] = i; //记录路径
            vis[tmp] = 1;
            if(tmp == T) return 1;
            q.push(tmp);
        }
    }
    return false;
}
int ans;
int EK() {
    int sub;
    while(BFS(st, ed)) {
        sub = 0x3fffff;
        int i = pre[ed];
        while(i != -1) {
            sub = min(sub, edge[i].va);
            i = pre[edge[i].fr];
        }
        i = pre[ed];
        while(i != -1) {
            edge[i].va -= sub; 
            edge[i ^ 1].va += sub;
            i = pre[edge[i].fr];
            /*
            我们要求剩余权值
            而如果每次将边权减去更新部分之后
            求个最小值 即可得到路径上的最小剩余
            反边就对应的加上这个最小剩余
            由于反边和当前边的编号一定相差1,
            所以可以直接^得到
            */
        }
        ans += sub;
    }
    return ans;
}
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    read(n), read(m), read(st), read(ed);
    int x, y, z;
    memset(head, -1, sizeof head);
    for(int i = 1; i <= m; ++i) {
        read(x), read(y), read(z);
        add_edge(x, y, z);
    }
    cout<<EK()<<endl;
    return 0;
}

其实EK的思路十分简单,就是无数次的BFS直到能够找到我们目标的最大流。
该方法luogu70,除了三个TLE之外都是0ms。该方法非常容易写,足够应付一些数据比较小的题。
考虑一下复杂度,据说是 O(VE2) ,显然不能接受。怎么来的?希腊奶。


Dinic算法

最负盛名的网络流算法,也是最为实用的。
我们先略过最小弧优化,考虑一下朴素的Dinic。

#include <bits/stdc++.h>
using namespace std;
#define MAXE 1000003
struct node{
    int fr, to, va;
    int nxt;
}edge[MAXE];
int head[MAXV], n, m, cnt, st, ed;
inline void read(int &x) {
    x = 0; char c = getchar();
    while(!isdigit(c)) c = getchar();
    while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
}
inline void add_edge(int u, int v, int w) {
    edge[cnt].fr = u, edge[cnt].to = v, edge[cnt].va = w;
    edge[cnt].nxt = head[u], head[u] = cnt++;
    edge[cnt].fr = v, edge[cnt].to = u, edge[cnt].va = 0;
    edge[cnt].nxt = head[v], head[v] = cnt++;
} 
int deep[MAXV];
bool BFS() {
    memset(deep, 0, sizeof deep);
    queue<int> q;
    q.push(st);
    deep[st] = 1;
    while(!q.empty()) {
        int tmp = q.front();
        q.pop();
        for(int i = head[tmp]; i != -1; i = edge[i].nxt) {
            int v = edge[i].to;
            if( deep[v] || edge[i].va <= 0) continue;
            deep[v] = deep[tmp] + 1;
            q.push(v);
        }
    }
    return deep[ed];
}
int dfs(int u, int flow) { 
    //flow为到达终点最多能增广的值
    if(u == ed) return flow;
    int add = 0; 
    //u点的最大增广量
    for(int i = head[u]; i != -1 && add < flow; i = edge[i].nxt) {
        int v = edge[i].to;
        if(deep[v] != deep[u] + 1) continue;
        if(!edge[i].va) continue;
        int tmpadd = dfs(v, min(edge[i].va, flow - add));
        edge[i].va -= tmpadd;
        edge[i ^ 1].va += tmpadd;
        add += tmpadd;
    }
    return add;
}
int Dinic() {
    int ans = 0; 
    while(BFS()) 
        ans += dfs(st, 0x3fffff);
    return ans;
}
int main() {
    memset(head, -1, sizeof head);
    scanf("%d%d%d%d", &n, &m, &st, &ed);
    int x, y, z;
    for(int i = 0; i < m; ++i) {
        read(x), read(y), read(z);
        add_edge(x, y, z);
    }
    cout<<Dinic()<<endl;
    return 0;
}

蒟蒻不会手动递归.jpg
考虑一下我们刚才的EK算法,我们首先BFS出了一个可行的最短路,然后以这个最短路为基准转移的。
…求豆麻袋。
为什么我们要增广一次就BFS一次啊,刚才我们在一次BFS中,求出了一条最短路,但是,考虑一棵最短路树,这个最短路只是这棵树一个小小的枝儿。既然如此,何不继续在这个树上搞呢?
首先,我们先BFS出从st到ed的路径距离,然后构造一棵最短路树,然后,我们对这棵最短路树进行DFS操作,最后把得到的结果加起来。完成。


参考资料

https://www.cnblogs.com/smartweed/p/5865727.html
http://www.cnblogs.com/zsboy/archive/2013/01/27/2878810.html

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值