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