【算法笔记】总结 - 网络流 Edmonds-Karp 算法和 dinic 算法

边的表示

1)
如果要把边单独存在一个buffer里面,并且每条边有一个唯一的编号。
那么通常将正反边存在一起,比如0,1,这样就可以用 id^1 来方便地访问反边了。
2)
也可以对每个节点,存一个 vector<Edge>
其中 rev 是在 to 节点的邻边列表中的编号。
可以只用 cap 记录剩余容量,这样就省掉 flow 了。

struct Edge {
    int to, cap, rev;
}

3)
对有向边,初始反边的容量为0。
对于无向边,在加边的时候,设置反边的容量与正边相同就好了。
这样就允许流可以从两个方向通过。

Edmonds-Karp 算法

使用BFS来找增广路径。
算法上界 O(VE2) ,证明见《算法导论》

struct Edge {
    int from, to, cap, flow;
    Edge(int u, int v, int c, int f):from(u), to(v), cap(c), flow(f) {}
};

struct EK {
    vector<Edge> edges;
    vector<int> g[N];
    int a[N], p[N];

    void add_edge(int u, int v, int c) {
        edges.push_back( Edge(u, v, c, 0) );
        edges.push_back( Edge(v, u, 0, 0) );
        int sz = edges.size();
        g[u].push_back( sz-2 );
        g[v].push_back( sz-1 );
    }

    int bfs(int s, int t) {
        queue<int> q;
        memset(a, 0, sizeof(a));
        a[s] = INF;
        q.push(s);
        while ( !q.empty() ) {
            int u = q.front(); q.pop();
            for(int i=0;i<g[u].size();++i) {
                int eid = g[u][i];
                Edge& e = edges[eid];
                if ( !a[e.to] && e.cap > e.flow ) {
                    p[e.to] = eid;
                    a[e.to] = min (a[e.from], e.cap - e.flow);
                    q.push(e.to);
                }
            }
            if ( a[t] ) break;
        }
        if ( !a[t] ) return 0;
        for ( int i = t; i != s; i = edges[p[i]].from ) {
            edges[p[i]].flow += a[t];
            edges[p[i]^1].flow -= a[t];
        }
        return a[t];
    }

    int GetMaxFlow(int s, int t) {
        int MaxFlow = 0, tmp;
        while ( tmp = bfs(s, t) ) MaxFlow += tmp;
        return MaxFlow;
    }

    void reset(int n) {
        edges.clear();
        rep(i, 0, n) g[i].clear();
    }
};

Dinic 算法

首先来看 EK 算法的一个性质,即每次找到的增广路的长度是非递减的。证明也可以去算导。
Dinic 就是一次性增广多条路径,完成一次增广后,增广路的长度至少增加1。
因此Dinic最多运行V次。并且因为使用了“记录current指针”(下面会具体讲解)的重要优化,每次DFS增广的复杂度为 O(VE) ,所以算法上界 O(V2E)

阻塞流(翻译自http://www.cs.cmu.edu/~15451/lectures/lec11/dinic.pdf )
考虑算法某个阶段,由BFS所构建的层次图 GL 。Level0 即 {s}。Level 1 包含所有 GR 中从s出发且剩余容量为正的边所指向的顶点。其他的层定义类似。一旦到达t, GL 构建完成。 GL 中只含相邻层之间的边。
我们只关心 GL 中以增序访问各层的路径,把这样的路径叫做 proper path。每当我们找到并增广这样的路径的时候, 一条从 level i 到 level i + 1 的边会被饱和(或者说,删除)。因此,若干次增广后(最多m次),将 GL 中将不存在 proper path。这样在 GL 中得到的流叫做阻塞流。

阻塞流算法
对每个节点,我们保存它到下一层的边的列表,并维护一个current变量对应正在增广中的当前边。初始化时,current指向每个列表的第一条边。随着算法进行,current逐渐“前进”。我们会不再考虑那些current**经过**的边。

在算法中,当 DFS(v) 返回时,它会告诉我们其是否找到了一条增广路。如果找到了,我们立即返回给调用者,告诉“path found”。如果没有找到,则current移向下一条边。当current到达末尾时,返回 “no path found”。

为什么current经过的边不会再被考虑?
1)
一条边 (u , v) 被判定为 useless, 仅当 DFS(v) 返回 “no path found”。因为此时从v出发的所有边都被遍历了,所以从v出发不可能找到增广路了。
2)
一条边 (u , v) 被判定为 useless 的边在将来仍将是 useless。因为增广只会删除从一层到下一层的边,并增加相反方向的边。

// 网络流 dinic
struct Edge {
    int to, cap, rev;
};

vector<Edge> G[MaxV+5];
int level[MaxV+5], iter[MaxV+5]; // 距离标号,当前边current

// 在残量网络里面加边
void add_edge(int from, int to, int cap) {
    G[from].push_back( (Edge){to, cap, G[to].size()} );
    G[to].push_back( (Edge){from, 0, G[from].size()-1} );
}

// bfs 构建层次
void bfs(int s) {
    memset(level, -1, sizeof(level));
    level[s] = 0;
    queue<int> q;
    q.push(s);
    while (!q.empty()) {
        int u = q.front();q.pop();
        int sz = G[u].size();
        for (int i=0;i<sz;++i) {
            Edge &e = G[u][i];
            if (e.cap > 0 && level[e.to] < 0) {
                level[e.to] = level[u] + 1;
                q.push(e.to);
            }
        }
    }
}

int dfs(int x, int t, int f) {
    if (x == t || f == 0) return f;
    int flow = 0, sz = G[x].size();
    for (int &i = iter[x]; i<sz;++i) {
        Edge &e = G[x][i];
        if (e.cap > 0 && level[x] < level[e.to]) {
            int d = dfs(e.to, t, min (f, e.cap));
            if (d > 0) {
                e.cap -= d;
                G[e.to][e.rev].cap += d;
                flow += d;
                f -= d;
                if (!f) break;
            }
        }
    }
    return flow;
}

int max_flow(int s, int t) {
    int flow = 0;
    for (;;) {
        bfs (s);
        if (level[t] < 0) return flow;
        memset(iter, 0, sizeof(iter));
        flow += dfs(s, t, inf);
    }
    return flow;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值