1. 程式人生 > 其它 >「Note」最小費用最大流 MCMF

「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;
}