最小费用流
- 所谓费用流就是,在普通的网络流当中,每一条边加上一个费用,求出一组解,使得在流量满足一定的情况下,费用的花费情况。
既然要求费用最小的情况,很容易想到要使用最短路径,把每一条边的费用当成距离,这样得到的路径费用总和是最小的,就可以用最短路径算法求从 s s s到 t t t的最短路径,再结合之前的沿着这条路径增广即可。不过在求最短距离的时候,要加一个条件,就是保证这条边有流量,否则就没有意义了
例如求最小费用最大流
题目描述
如题,给出一个网络图,以及其源点和汇点,每条边已知其最大流量和单位流量费用,求出其网络最大流和在最大流情况下的最小费用。
输入
第一行包含四个正整数 N , M , S , T , N,M,S,T, N,M,S,T,分别表示点的个数、有向边的个数、源点序号、汇点序号。
其中 N ⩽ 5000 , M ⩽ 50000 N \leqslant 5000,M \leqslant 50000 N⩽5000,M⩽50000
接下来 M M M行每行包含四个正整数 u i 、 v i 、 w i 、 f i u_i、v_i、w_i、f_i ui、vi、wi、fi,表示第i条有向边从 u i u_i ui出发,到达 v i v_i vi,边权为 w i w_i wi(即该边最大流量为 w i w_i wi),单位流量的费用为 f i f_i fi。
输入样例
4 5 4 3
4 2 30 2
4 3 20 3
2 3 20 1
2 1 30 9
1 3 40 5
输出样例
50 280
![](https://i-blog.csdnimg.cn/blog_migrate/5073f2b2beb7215a07fba615af90267c.png)
源点是 4 4 4,汇点是 3 3 3
思路就是用bellman-ford
找到一条从
s
s
s到
t
t
t的最短路径,同时记录下路径,因为后面要修改流量,同时由于是单位费用,找到这条路所带来的流量然后乘以到达
t
t
t的最短距离就是这条路的费用,不断找最短路径,直到不能增广为止
代码
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cctype>
#define min(a,b) a>b?b:a
#define M 50005
#define N 5005
#define INF 0x3f3f3f3f
#define in(a) a = read()
inline int read () {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)) {if(c == '-') f = -1; c = getchar();}
while(isdigit(c)) {x = (x << 3) + (x << 1) + c - 48; c = getchar();}
return f*x;
}
using namespace std;
int n, m, s, t, cnt;
int maxflow; //最大流
int dis[N]; //dis[i]表示从s到达i点的最短路径
int preV[N], preE[M]; //preV[i]表示顶点i的前置节点,preE[i]表示顶点i的前一条边
struct Edge {
int from, to, cap, cost;
}a[M * 2];
int bellman_ford () {
int res;
memset(dis, INF, sizeof(dis));
dis[s] = 0;
bool update = true;
while (update) {
update = false;
for(int i = 0; i < cnt; i++) {
int u = a[i].from, v = a[i].to;
if(dis[u] != INF && a[i].cap > 0 && dis[v] > dis[u] + a[i].cost) {
dis[v] = dis[u] + a[i].cost;
preE[v] = i;
preV[v] = u;
update = true;
}
}
}
if(dis[t] == INF || dis[t] < 0) return -1;
int flow = INF;
for(int v = t; v != s; v = preV[v])
flow = min(flow, a[preE[v]].cap);
res = flow * dis[t];
maxflow += flow;
//增广
for(int v = t; v != s; v = preV[v]) {
a[preE[v]].cap -= flow; //正边减去流量
a[preE[v]^1].cap += flow; //反边加上流量
}
return res;
}
int main () {
int u, v, w, f;
in(n), in(m), in(s), in(t);
for(int i = 1; i <= m; i++) { //所有的正边都是偶数,反边都是奇数
in(u), in(v), in(w), in(f);
a[cnt].from = u, a[cnt].to = v, a[cnt].cap = w, a[cnt].cost = f;
cnt++;
a[cnt].from = v, a[cnt].to = u, a[cnt].cap = 0, a[cnt].cost = -f;
cnt++;
}
int ans = 0, t;
while(1) {
t = bellman_ford();
if(t == -1) break;
ans += t;
}
printf("%d %d", maxflow, ans);
return 0;
}
为了让算法效率更快,可以使用 D i j k s t r a Dijkstra Dijkstra算法求解,众所周知, D i j k s t r a Dijkstra Dijkstra算法无法判断负环。为了消除这一影响,需要人为的给每一个节点加上一个势 h h h,使得原来的一条边 e ( u , v ) e(u,v) e(u,v)变成 e ′ ( u , v ) = e ( u , v ) + h ( u ) − h ( v ) e'(u,v) = e(u,v) + h(u)-h(v) e′(u,v)=e(u,v)+h(u)−h(v),只要保证 e ′ ( u , v ) ⩾ 0 e'(u,v) \geqslant 0 e′(u,v)⩾0就可以使用 D i j k s t r a Dijkstra Dijkstra算法了
现在对于原来的每一条边 e ( u , v ) e(u,v) e(u,v),边的权值变成了 w ′ ( u , v ) = w ( u , v ) + h [ u ] − h [ v ] w'(u,v) = w(u,v) + h[u] - h[v] w′(u,v)=w(u,v)+h[u]−h[v]。
假设有一条源点 s s s到 t t t的路径, p 1 , p 2 , p 3 . . . p n p_1,p_2,p_3...p_n p1,p2,p3...pn,这条路径的边权之和为
( w 1 + w 2 + . . . + w n − 1 ) + ( h 1 − h 2 + h 2 − h 3 + h 3 − h 4 + . . . h n − 1 − h n ) (w_1+w_2+...+w_{n-1})+(h_1-h_2+h_2-h_3+h_3-h_4+...h_{n-1}-h_n) (w1+w2+...+wn−1)+(h1−h2+h2−h3+h3−h4+...hn−1−hn)
相当于 ∑ i = 1 n − 1 w i + h 1 − h n \sum^{n-1}_{i=1}w_i + h_1-h_n ∑i=1n−1wi+h1−hn,只要算出这个路线的最短路之后,减去 h s − h t h_s - h_t hs−ht就可以得到原来的最短路了
那么现在就要考虑怎么求这个势 h h h了
假设 d i s [ i ] dis[i] dis[i]表示从起点到达 i i i的最短路径,则一定有
- d i s [ v ] ⩽ d i s [ u ] + e ( u , v ) − > d i s [ u ] + e ( u , v ) − d i s [ v ] ⩾ 0 dis[v] \leqslant dis[u] + e(u,v) \,\,\,\,\,\,\,\, -> \,\,\,\,\,\,\,dis[u] + e(u,v) - dis[v] \geqslant 0 dis[v]⩽dis[u]+e(u,v)−>dis[u]+e(u,v)−dis[v]⩾0
因为 d i s [ u ] dis[u] dis[u]表示到 u u u点的最短距离, d i s [ v ] dis[v] dis[v]表示到 v v v的最短距离,而到 v v v的最短距离不一定是从 ( u , v ) (u,v) (u,v)这条边转移过来的。
显然,可以把到达这个点的最短距离当做这个点的势,这样图中就不会有负权边了,就可以跑 D i j k s t r a Dijkstra Dijkstra算法了
#include <cstdio>
#include <queue>
#include <bits/stdc++.h>
#include <iostream>
#include <cstring>
#define N 5005
#define M 50005
#define INF 0x7fffffff
#define P pair<int, int >
using namespace std;
inline int min (int x, int y) {
return x > y ? y : x;
}
int n, m, s, t, cnt = -1, maxflow, mincost;
int head[N], dis[N], h[N], preE[N], preV[N];
struct Edge {
int next, to, cap, cost;
}edge[M * 2];
inline void Ad (int u, int v, int c, int w) {
edge[++cnt].next = head[u], head[u] = cnt;
edge[cnt].to = v, edge[cnt].cap = c, edge[cnt].cost = w;
}
inline void add (int u, int v, int c, int w) {
Ad(u, v, c, w), Ad(v, u, 0, -w);
}
priority_queue <P, vector<P >, greater <P > > q;
void dijkstra () {
while (!q.empty ()) q.pop();
memset (dis, -1, sizeof (dis));
dis[s] = 0, q.push (P (0, s));
while (!q.empty ()) {
P cur = q.top (); q.pop ();
int u = cur.second;
if (dis[u] < cur.first) continue; //点可能被更新多次
for (int i = head[u]; i != -1; i = edge[i].next) {
if (!edge[i].cap) continue; //保证要有流量
int v = edge[i].to; //这条边指向的顶点
if (dis[v] < 0 || dis[v] > dis[u] + edge[i].cost + h[u] - h[v]) {
dis[v] = dis[u] + edge[i].cost + h[u] - h[v];
preV[v] = u, preE[v] = i;
q.push (P (dis[v], v));
}
}
}
}
void solve () {
memset (h, 0, sizeof (h));
for (int f = INF; f > 0;) {
dijkstra ();
if (dis[t] < 0) break;
for (int i = 1; i <= n; i++) //更新势(很重要)
h[i] += (dis[i] == -1 ? 0 : dis[i]);
int flow = f;
for (int i = t; i != s; i = preV[i]) //找到增广路带来的流量
flow = min (flow, edge[preE[i]].cap);
maxflow += flow, f -= flow;
mincost += flow * h[t];
for (int i = t; i != s; i = preV[i]) {
edge[preE[i]].cap -= flow;
edge[preE[i] ^ 1].cap += flow;
}
}
}
int main () {
int u, v, ca, co;
scanf ("%d %d", &n, &m);
s = 1, t = n;
memset (head, -1, sizeof (head));
for (int i = 1; i <= m; i++) {
scanf ("%d %d %d %d", &u, &v, &ca, &co);
add(u, v, ca, co);
}
solve ();
cout << maxflow << " " << mincost;
return 0;
}
未完待续。。。