EK算法

前言

最大流中的 EK 算法可能会略微逊色于 dinic 算法,但是对于费用流的实现有着很大的作用

费用流有很多实现它的算法, 这里只讲用EK算法实现的费用流,他的思路其实与EK算法求最大流相差无几。

最大流

定义

对于一张图 \(G = (V, E)\),每条边都会有一个容量,从原点引入水流,水流要通过边流到汇点,水流不可以超过边的容量,而最大流就是求一种方案使得可以从流最多的水到达汇点。

当然,网络流肯定满足以下性质:
\(f(u, v)\) 为这条边的流量,\(c(u, v)\) 为这条边的限制

  1. 容量限制:对于每条边,流经该边的流量不得超过该边的容量,即 \(0 \leq f(u, v) \leq c(u, v)\)
  2. 流量守恒:除源汇点外,任意结点 \(u\) 的净流量为 \(0\)其中,我们定义 \(u\) 的净流量为 \(f(u) = \sum_{x \in V} f(u, x) - \sum_{x \in V} f(x, u)\)

思路

  1. bfs找一条从源点到起点的路径,并求出这条路径中的最小容量
  2. 将这条路径的每一条边减去最小容量,在建一条反向边,权值为最小容量

一下面的图为例:

执行第一步操作,找到一条路径

执行第二步操作,更新路径权值

继续循环执行第一步

执行第二步操作

最后,发现再也没有一条路径可以从源点通往汇点,结束操作

这就是EK的基本操作,但是它的时间复杂度偏高,为 \(O(nm^2)\)

具体优化可以用 dinic算法,可以优化成 \(O(n^2m)\),对于稠密图,dinic算法的优势就能展现出来了

代码实现

bool bfs() {
    memset(vis, false, sizeof vis);
    queue<int> q;
    q.push(S); vis[S] = true;
    dist[S] = 0x3f3f3f3f;
    while (q.size()) {
        int u = q.front(); q.pop();
        for (int i = 0; i < g[u].size(); i++) {
            int v = g[u][i].v, w = g[u][i].w;
            if (!w || vis[v]) continue;
            prenode[v] = u;
            preedge[v] = i;
            q.push(v);
            vis[v] = true;
            dist[v] = min(dist[u], w);
            if (v == T) return true;
        }
    }
    return false;
}

int EK() {
    int res = 0;
    while (bfs()) {
        res += dist[T];
        for (int i = T; i != S; i = prenode[i]) {
            int u = prenode[i], v = i, vid = preedge[i];
            g[u][vid].w -= dist[T],
            g[v][g[u][vid].id].w += dist[T];
        }
    }
    return res;
}

费用流

定义

给定一个网络 \(G = (V, E)\),每条边除了有容量限制 \(c(u, v)\),还有一个单位流量的费用 \(w(u, v)\),费用流就是所有的最大可行流中所用的费用最小的方案,其中费用的定义为每条边所流过的流量乘上费用,即为 \(\sum_{(u, v) \in E} f(u, v) \cdot w(u, v)\),其中 \(f(u, v)\),为这条边的流量

思路

和最大流非常相像,只要将最大流第一步中找一条从源点到汇点的路径改为找一条从源点到汇点的每条边以费用为权值最短路径即可

这里不再演示,与EK求最大流思路非常相似

代码实现

bool bfs() {
    queue<int> q; q.push(S);
    memset(dist, 0x3f, sizeof dist);
    dist[S] = 0;
    memset(mn, 0x3f, sizeof mn);
    while (q.size()) {
        int u = q.front(); q.pop();
        for (int i = 0; i < g[u].size(); i++) {
            int v = g[u][i].v, w = g[u][i].w, cost = g[u][i].cost;
            if (!w) continue;
            if (dist[v] > dist[u] + cost) {
                dist[v] = dist[u] + cost;
                q.push(v);
                prenode[v] = u;
                preedge[v] = i;
                mn[v] = min(mn[u], w);
            }
        }
    }
    return dist[T] != 0x3f3f3f3f;
}

void EK() {
    int res = 0, Cost = 0;
    while (bfs()) {
        res += mn[T], Cost += mn[T] * dist[T];
        for (int i = T; i != S; i = prenode[i]) {
            int u = prenode[i], v = i, vid = preedge[v];
            g[u][vid].w -= mn[T];
            g[v][g[u][vid].id].w += mn[T];
        }
    }
    cout << res << " " << Cost << endl;
}

后记

最大流和网络流在竞赛中用的相当广泛,也有很多的应用如:最小割等,并且希望这篇博客对你们有帮助。

posted @ 2025-07-12 12:03  wuzihenb  阅读(40)  评论(0)    收藏  举报