「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 條邊組成且權值更大的路徑跑出來更小,因為加的極大值更少。
所以考慮在點上做點工作。
我們給每個點加上個 勢能函式
將邊 \(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;
}