「Note」最小费用最大流 MCMF

关于 ta ,有几种不同的实现方法。(我以为觉得 EK 好打些

SPFA + EK

在费用流的题目中会出现负权的情况,所以很自然的就会想到使用 SPFA 来应付负权。

EK 中,我们会找到最短的路径来进行增广,所以我们可以把这个过程用 SPFA 求最短路来实现,将费用作为边权来跑。

其他的过程还是跟 EK 求最大流的过程差不多。

设最大流量为 \(F\) ,过程中每次增广的流量至少为 \(1\) ,所以至多增广 \(F\) 次。

而单次 SPFA 最坏复杂度为 \(\operatorname{O(V*E)}\) ,所以该算法的最坏复杂度为 \(\operatorname{O(F*V*E)}\)

Code

//Luogu P3381
#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>

using namespace std;

#define Maxn 5000
#define Int int
#define LL long long
#define rep(i, j, k) for(int i = (j); i <= (k); i ++)
#define per(i, j, k) for(int i = (j); i >= (k); i --)

int n, m, s, t;
Int dis[Maxn + 5], cap[Maxn + 5];
int fa[Maxn + 5], vis[Maxn + 5], idx[Maxn + 5];

struct Edge {
    int v, id;
    Int flow, cst;
} ;

vector < Edge > edge[Maxn + 5];

void Add_Edge (int u, int v, Int f, Int c) {
    int idu = edge[u].size ();
    int idv = edge[v].size ();
    edge[u].push_back ({v, idv, f, c});
    edge[v].push_back ({u, idu, 0, -c});
}

bool Spfa (int S, int T) {
    queue < int > que;
    memset (vis, 0, sizeof vis);
    memset (dis, 0x3f, sizeof dis);
    memset (cap, 0x3f, sizeof cap);

    vis[S] = 1, dis[S] = 0, fa[T] = 0;

    que.push (S);
    while (que.size ()) {
        int u = que.front (); que.pop ();
        vis[u] = 0;
        rep (i, 0, (int) edge[u].size () - 1) {
            int v = edge[u][i].v;
            Int f = edge[u][i].flow, c = edge[u][i].cst;
            if (f > 0 && dis[v] > dis[u] + c) {
                fa[v] = u;
                idx[v] = i;
                dis[v] = dis[u] + c;
                cap[v] = min (cap[u], f);
                if (!vis[v]) {
                    vis[v] = 1;
                    que.push (v);
                }
            }
        }
    }

    return fa[T];
}

void EK (int S, int T) {
    Int Flow = 0, Cost = 0;
    while (Spfa (S, T)) {
        Flow += cap[T];
        Cost += cap[T] * dis[T];
        for (int v = T, u; v ^ S; v = fa[v]) {
            u = fa[v];
            edge[u][idx[v]].flow -= cap[T];
            edge[v][edge[u][idx[v]].id].flow += cap[T];
        }
    }
    printf ("%d %d", Flow, Cost);
}


int main () {
    scanf ("%d %d %d %d", &n, &m, &s, &t);

    rep (i, 1, m) {
        int u, v, w, c;
        scanf ("%d %d %d %d", &u, &v, &w, &c);
        Add_Edge (u, v, w, c);
    }

    EK (s, t);
    return 0;
}

Dijkstra + EK

上面辣个算法的本质就是将 EK 用最短路找路径来增广。

由于SPFA已经死了 SPFA 容易被卡,所以很难不把厚望寄托在 Dij 上,但对于 Dij 负权是一个大问题。(众所周知 ta 不能解决带负权的最短路

所以 How to 解决负权的问题呢 ???

很直接的想法:给每条边的费用加上一个极大值,使得每条边的边权都变正。

乍一听挺有道理,但细细一想:这玩意儿会受边数影响。

比如:在原图中一条由 5 条边组成的路径是最短路,但可能按这样跑会使得原图上一条由 4 条边组成且权值更大的路径跑出来更小,因为加的极大值更少。

所以考虑在点上做点工作。

我们给每个点加上个 势能函数 \(h\)

将边 \(u \rightarrow v\) 的权值改为 \(w'=h[u]-h[v]+w[u][v]\) ,并且所有 \(w' \geq 0\)

如何求出 \(h\) 呢?稍微变形 : \(h[u]+w[u][v]>=h[v]\) 。容易看出这就是最短路的不等式,所以最初的 \(h\) 跑一遍 SPFA 就行了 (怕有负权,一遍而已无伤大雅

但在增广后,路径上的边的反边也可能会被增广到,所以原本的 \(h\) 并不能满足所有边。

\(u\rightarrow v\) 在上一次增广的路径上,所以 \(dis[v]=dis[u]+h[u]-h[v]+w[u][v]\)\(dis[u]+h[u]-(dis[v]+h[v])+w[u][v]=0\) 所以将 \(h[u]+=dis[u]\) 就好了。

Code

//Luogu P3381
#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>

using namespace std;

#define Int int
#define Maxn 5000
#define LL long long
#define INF 0x3f3f3f3f
#define Pii pair < int , int >
#define rep(i, j, k) for(int i = (j); i <= (k); i ++)
#define per(i, j, k) for(int i = (j); i >= (k); i --)

int n, m, s, t;
Int dis[Maxn + 5], cap[Maxn + 5], h[Maxn + 5];
int fa[Maxn + 5], vis[Maxn + 5], idx[Maxn + 5];

struct Edge {
    int v, id;
    Int flow, cst;
} ;

vector < Edge > edge[Maxn + 5];

void Add_Edge (int u, int v, Int f, Int c) {
    int idu = edge[u].size ();
    int idv = edge[v].size ();
    edge[u].push_back ({v, idv, f, c});
    edge[v].push_back ({u, idu, 0, -c});
}

void Spfa (int S, int T) {
    queue < int > que;
    memset (vis, 0, sizeof vis);
    memset (dis, 0x3f, sizeof dis);
    memset (cap, 0x3f, sizeof cap);

    vis[S] = 1, dis[S] = 0, fa[T] = 0;

    que.push (S);
    while (que.size ()) {
        int u = que.front (); que.pop ();
        vis[u] = 0;
        rep (i, 0, (int) edge[u].size () - 1) {
            int v = edge[u][i].v;
            Int f = edge[u][i].flow, c = edge[u][i].cst;
            if (f > 0 && dis[v] > dis[u] + c) {
                dis[v] = dis[u] + c;
                if (!vis[v]) {
                    vis[v] = 1;
                    que.push (v);
                }
            }
        }
    }
}

bool Dijkstra (int S, int T) {
    priority_queue < Pii > que;
    memset (fa, 0, sizeof fa);
    memset (idx, 0, sizeof idx);
    memset (vis, 0, sizeof vis);
    memset (dis, 0x3f, sizeof dis);
    memset (cap, 0x3f, sizeof cap);

    dis[S] = 0, fa[T] = 0;

    que.push (make_pair (0, S));
    while (que.size ()) {
        Pii now = que.top (); 
        que.pop ();
        int u = now.second;
        if (vis[u]) continue;
        vis[u] = 1;
        rep (i, 0, (int) edge[u].size () - 1) {
            int v = edge[u][i].v;
            Int f = edge[u][i].flow;
            Int c = h[u] - h[v] + edge[u][i].cst;
            if (f > 0 && dis[v] > dis[u] + c) {
                fa[v] = u;
                idx[v] = i;
                dis[v] = dis[u] + c;
                cap[v] = min (cap[u], f);
                que.push (make_pair (-dis[v], v));
            }
        }
    }

    return fa[T];
}

void EK (int S, int T) {
    Int Flow = 0, Cost = 0;
    Spfa (S, T);
    rep (i, 1, n) h[i] = dis[i];
    int tot = 0;
    while (Dijkstra (S, T)) {
        rep (i, 1, n) h[i] = min (dis[i] + h[i], INF);
        Flow += cap[T];
        Cost += cap[T] * (h[T] - h[S]); //h'[T]=h[T]+dis[T]
        for (int v = T, u; v ^ S; v = fa[v]) {
            u = fa[v];
            edge[u][idx[v]].flow -= cap[T];
            edge[v][edge[u][idx[v]].id].flow += cap[T];
        }
    }
    printf ("%d %d", Flow, Cost);
}

int main () {
    scanf ("%d %d %d %d", &n, &m, &s, &t);

    rep (i, 1, m) {
        int u, v, w, c;
        scanf ("%d %d %d %d", &u, &v, &w, &c);
        Add_Edge (u, v, w, c);
    }

    EK (s, t);
    return 0;
}
posted @ 2022-01-06 21:57  PoisonNNN  阅读(33)  评论(0编辑  收藏  举报