一般求解费用流运用EK,然后可以单路或多路增广。
注意这里的多路增广要求一个点在一次dfs中不能被经过两次。因为可能出现零环或负环的情况。
而单路增广虽然效率低,但好写许多且在某些已经限制了总流量且单次流量都为1时有它的好处。
但在有些稠密图中,即使多路增广实际效果也不好,这时候可以采取dijkstra代替spfa。
这个跟johnson算法很像很像,也是维护一个势函数。
但与johnson算法不同的是,我们并不需要从原点向每个点连一条为0的边。
我们则可以暂且把势函数先全部赋值为0。
这是因为,在费用流的图中有一点很重要,就是一条负边你不可能一开始就走,必须是走过它正边以后把它抵消回去,换句话说,在费用流的图中一开始是绝对不会出现负环的(即使你一开始连的是负边,你依然不会走出负环)。所以这种情况下我们不需要像johnson一样求一遍spfa。
现在需要的是维护这个势函数,可以发现只需要把势函数加上这一遍dij求出来的 d i s dis dis值即可。
注意一点的是,求dijkstra的时候要注意一个小优化。
Code
多路增广
#pragma GCC optimize(3,"Ofast","inline")
#include <bits/stdc++.h>
#define I register int
#define F(i, a, b) for (int i = a; i <= b; i++)
#define min(a, b) ((a) < (b) ? (a) : (b))
#define mem(a, b) memset(a, b, sizeof a)
const int N = 400 + 10;
const int inf = 1e9;
using namespace std;
int n, m, x, y, c, w, tot, S,T, Ans, Sum, dis[N], h[N], cur[N]; bool vis[N];
struct edge { int to, flow, cost, rev; };
vector <edge> G[N];
struct node {
int x, dis;
bool operator < (const node &a) const { return a.dis < dis; } //注意这里不能用friend去定义的,自定义的a就是要排的数,然后与当前这个位置dis进行比较
};
void ins(I x, I y, I w, I l) {
G[x].push_back({y, w, l, G[y].size()});
G[y].push_back({x, 0, - l, G[x].size() - 1});
} //当边数很多时用vector会显现优势,在O2下优势明显!!!
priority_queue <node> Q;
bool dijkstra() {
mem(dis, 127), dis[S] = 0, Q.push({S, 0});
while (Q.size()) {
I k = Q.top().x, v = Q.top().dis; Q.pop();
if (v > dis[k]) continue; //注意这个trick,这是当边数很多时,dis[k]有可能被更新很多次,但显然最小的那个才有用,所以我们只更新一次。这样可以保证复杂度是O(n^2+mlogn)的
F(i, 0, G[k].size() - 1) {
edge e = G[k][i];
if (e.flow && dis[e.to] > dis[k] + e.cost + h[k] - h[e.to]) {
dis[e.to] = dis[k] + e.cost + h[k] - h[e.to];
Q.push({e.to, dis[e.to]});
}
}
}
return dis[T] < inf;
}
int DFS(I k, I flow) {
if (k == T) return flow;
I have = 0;
vis[k] = 1;
for (I &i = cur[k]; i < G[k].size(); i ++) { //当前弧优化
edge &e = G[k][i];
if (!vis[e.to] && e.flow && dis[e.to] == dis[k] + e.cost + h[k] - h[e.to]) {
I now = DFS(e.to, min(flow - have, e.flow));
e.flow -= now, G[e.to][e.rev].flow += now;
have += now;
if (flow == have) break;
}
}
vis[k] = 0; //这里是多路增广的一个trick,但要注意当边数和点数不在一个量级时,这个要省略,也就是说我们尝试单路增广
return have;
}
int main() {
scanf("%d%d", &n, &m), tot = 1, S = 1, T = n;
F(i, 1, m) {
scanf("%d%d%d%d", &x, &y, &c, &w);
ins(x, y, c, w);
}
Ans = 0;
while (dijkstra()) {
mem(cur, 0);
int Flow = DFS(S, inf);
F(i, S, T) h[i] += dis[i];
Sum += Flow, Ans += h[T] * Flow; // h[S]一定为0,所以可以省略。
}
printf("%d %d\n", Sum, Ans);
}
单路增广
#include <bits/stdc++.h>
#define F(i, a, b) for (int i = a; i <= b; i++)
#define min(a, b) ((a) < (b) ? (a) : (b))
#define mem(a, b) memset(a, b, sizeof a)
const int N = 400 + 10;
const int M = 3e5 + 10;
const int inf = 2e9;
using namespace std;
int n, m, x, y, c, w, tot, S,T, Ans, Sum;
bool vis[N], bz[N], h[N];
int tov[M], nex[M], len[M], cost[M], pre[N], las[N], dis[N];
struct node {
int x, dis;
bool operator < (const node &a) const { return a.dis < dis; }
};
void ins(int x, int y, int c, int w) {
tov[++tot] = y, nex[tot] = las[x], las[x] = tot, len[tot] = c, cost[tot] = w;
tov[++tot] = x, nex[tot] = las[y], las[y] = tot, len[tot] = 0, cost[tot] = -w;
}
bool dijkstra() {
F(i, 1, n) {
h[i] = min(h[i] + dis[i], inf);
dis[i] = i == S ? 0 : inf;
}
priority_queue <node> Q; Q.push({S, 0}); mem(vis, 0);
while (Q.size()) {
int k = Q.top().x, v = Q.top().dis; Q.pop();
if (v > dis[k]) continue;
for (int x = las[k]; x ; x = nex[x])
if (len[x] && dis[tov[x]] > dis[k] + cost[x] + h[k] - h[tov[x]]) {
dis[tov[x]] = dis[k] + cost[x] + h[k] - h[tov[x]];
pre[tov[x]] = x;
Q.push({tov[x], dis[tov[x]]});
}
}
return dis[T] < inf;
}
int main() {
scanf("%d%d", &n, &m), tot = 1, S = 1, T = n;
F(i, 1, m) {
scanf("%d%d%d%d", &x, &y, &c, &w);
ins(x, y, c, w);
}
while (dijkstra()) {
Ans += dis[T] + h[T] - h[S];
int Flow = 2147483647;
for (x = T; x ^ S; x = tov[pre[x] ^ 1])
Flow = min(Flow, len[pre[x]]);
Sum += Flow;
for (x = T; x ^ S; x = tov[pre[x] ^ 1])
len[pre[x]] -= Flow, len[pre[x] ^ 1] += Flow;
}
printf("%d %d\n", Sum, Ans);
}