最小费用最大流,指在保证最大流量的同时最小费用。
网络流系列文章
概念
费用
我们定义一条边的费用 $w(u,v)$ 表示边 $(u,v)$ 上单位流量的费用。也就是说,当边 $(u,v)$ 的流量为 $f(u,v)$ 时,需要花费 $f(u,v)\times w(u,v)$ 的费用。
最小费用最大流
网络流图中,花费最小的最大流被称为最小费用最大流,这也是接下来我们要研究的对象。
求解
我们可以在 $\text{Dinic}$ 算法的基础上进行改进,把 $\text{BFS}$ 求分层图改为用 $\text{SPFA}$(由于有负权边,所以不能直接用 $\text{Dijkstra}$)来求一条单位费用之和最小的路径,也就是把 $w(u,v)$ 当做边权然后在残量网络上求最短路,当然在 $\text{DFS}$ 中也要略作修改。这样就可以求得网络流图的最小费用最大流了。
如何建反向边?对于一条边 $(u,v,w,c)$(其中 $w$ 和 $c$ 分别为容量和费用),我们建立正向边 $(u,v,w,c)$ 和反向边 $(v,u,0,-c)$(其中 $-c$ 是使得从反向边经过时退回原来的费用)。
优化:如果你是“关于 $\text{SPFA}$,它死了”言论的追随者,那么你可以使用 $\text{Primal-Dual}$ 原始对偶算法将 $\text{SPFA}$ 改成 $\text{Dijkstra}$!
时间复杂度:可以证明上界为 $\mathcal O(nmf)$,其中 $f$ 表示流量。
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
const int N = 1e5 + 5, M = 2e5 + 5;
const int INF = 0x7f7f7f7f;
int n, m, s, t, dis[N];
int tot = 1, lnk[N], cur[N], ter[M], nxt[M], cap[M], cost[M];
bool vis[N];
void add(int u, int v, int w, int c) {
ter[++tot] = v, nxt[tot] = lnk[u], lnk[u] = tot, cap[tot] = w, cost[tot] = c;
}
void addEdge(int u, int v, int w, int c) {
add(u, v, w, c), add(v, u, 0, -c);
}
bool spfa(int s, int t) {
memset(dis, 0x7f, sizeof(dis));
memcpy(cur, lnk, sizeof(lnk));
std::queue<int> q;
q.push(s), dis[s] = 0, vis[s] = true;
while (!q.empty()) {
int u = q.front();
q.pop(), vis[u] = false;
for (int i = lnk[u]; i; i = nxt[i]) {
int v = ter[i];
if (cap[i] && dis[v] > dis[u] + cost[i]) {
dis[v] = dis[u] + cost[i];
if (!vis[v]) q.push(v), vis[v] = true;
}
}
}
return dis[t] < INF;
}
int dfs(int u, int t, int flow) {
if (u == t) return flow;
vis[u] = true;
int ans = 0;
for (int &i = cur[u]; i; i = nxt[i]) {
int v = ter[i];
if (cap[i] && !vis[v] && dis[v] == dis[u] + cost[i]) {
int x = dfs(v, t, std::min(cap[i], flow));
if (!x) continue;
cap[i] -= x, cap[i ^ 1] += x, flow -= x, ans += x;
if (!flow) break;
}
}
vis[u] = false;
return ans;
}
std::pair<int, int> dinic(int s, int t) {
int maxFlow = 0, minCost = 0;
while (spfa(s, t)) {
int x;
while ((x = dfs(s, t, INF))) maxFlow += x, minCost += x * dis[t];
}
return std::make_pair(maxFlow, minCost);
}
int main() {
scanf("%d%d%d%d", &n, &m, &s, &t);
for (int i = 1; i <= m; i++) {
int u, v, w, c;
scanf("%d%d%d%d", &u, &v, &w, &c);
addEdge(u, v, w, c);
}
std::pair<int, int> ans = dinic(s, t);
printf("%d %d\n", ans.first, ans.second);
return 0;
}
习题
网络流 24 题
- 「LOJ 6000」搭配飞行员
- 「LOJ 6001」太空飞行计划
- 「LOJ 6002」最小路径覆盖
- 「LOJ 6003」魔术球
- 「LOJ 6004」圆桌聚餐
- 「LOJ 6005」最长递增子序列
- 「LOJ 6006」试题库
- 「LOJ 6007」方格取数
- 「LOJ 6008」餐巾计划
- 「LOJ 6009」软件补丁
- 「LOJ 6010」数字梯形
- 「LOJ 6011」运输问题
- 「LOJ 6012」分配问题
- 「LOJ 6013」负载平衡
- 「LOJ 6014」最长 k 可重区间集
- 「LOJ 6015」星际转移
- 「LOJ 6121」孤岛营救问题
- 「LOJ 6122」航空路线问题
- 「LOJ 6223」汽车加油行驶问题
- 「LOJ 6224」深海机器人问题
- 「LOJ 6225」火星探险问题
- 「LOJ 6226」骑士共存问题
- 「LOJ 6227」最长 k 可重线段集问题