本篇博文将会重点讲解 dinic 求解费用流。
费用流全称:最小费用最大流,其一般的问题描述如下:
给出一张网络 G = < V , E > G=
f f f 表示这条边的最大流量, v v v 表示单位花费,也就是说从这条边每流过一单位流量就要增加 v v v 的花费。
现在要求这张网络的最小费用最大流,也就是在保证总流量最大的情况下总费用最小。
之前笔者写过一篇 EK 求解费用流,效率挺高,但是根据某法律:卡 EK 合法,卡 dinic 不合法!(笔者是真的不知道这法律哪来的)
于是 dinic 求解费用流还是需要掌握的。
在往下看之前,读者应当对以下知识有所了解:
没有学过?
模板题:P3381 【模板】最小费用最大流
首先回顾一下 dinic 求解最大流的思路:
先采用 BFS 分层,在相邻两层之间推流,采用 DFS 形式推流,一次可以找到多条增广路,采用当前弧优化。
那么 dinic 如何求费用流呢?
因为出现了最小费用这一限制条件,因此我们不能简单的对图分层,而是先需要跑一遍最短路径。
跑最短路径的时候要特别注意:反向边的费用为正向边费用的相反数,因此 不能直接使用 dijkstra 算法。
然后就可以愉快的 dinic 了~
因为此时已经满足了最短路径这一限制条件,也就是保证费用最小,因此此时直接类比 dinic 跑 DFS 就好了。
需要注意的是这里的 dinic 有一个特别的限制条件:走过的点不能再走,这是为了防止被环卡掉。
记录最小费用在中间记录就好。
代码:
/*
========= Plozia =========
Author:Plozia
Problem:P3381 【模板】最小费用最大流——dinic 写法
Date:2021/3/30
========= Plozia =========
*/
#include
using std::queue;
typedef long long LL;
const int MAXN = 5e3 + 10, MAXM = 5e4 + 10;
const LL INF = 0x3f3f3f3f3f3f3f3f;
int n, m, s, t, Head[MAXN], cnt_Edge = 1, cur[MAXN];
LL dis[MAXN], ans_Flow, ans_Spend;
bool book[MAXN], vis[MAXN];
struct node{int to; LL Flow, val; int Next;} Edge[MAXM << 1];
int read()
{
int sum = 0, fh = 1; char ch = getchar();
for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = (sum << 3) + (sum << 1) + (ch ^ 48);
return (fh == 1) ? sum : -sum;
}
void add_Edge(int u, int v, int w, int c) {++cnt_Edge; Edge[cnt_Edge] = (node){v, w, c, Head[u]}; Head[u] = cnt_Edge;}
LL Min(LL fir, LL sec) {return (fir < sec) ? fir : sec;}
bool SPFA()
{
queue <int> q; q.push(s);
memset(book, 0, sizeof(book));
memset(dis, 0x3f, sizeof(dis));
book[s] = 1; dis[s] = 0;
while (!q.empty())
{
int x = q.front(); q.pop(); book[x] = 0;
for (int i = Head[x]; i; i = Edge[i].Next)
{
int u = Edge[i].to;
if (Edge[i].Flow && dis[u] > dis[x] + Edge[i].val)
{
dis[u] = dis[x] + Edge[i].val;
if (!book[u]) {q.push(u); book[u] = 1;}
}
}
}
return dis[t] != INF;
}
LL dfs(int now, LL Flow)
{
if (now == t || Flow == 0) return Flow;
vis[now] = 1; LL used = 0;
for (int i = cur[now]; i; i = Edge[i].Next)
{
int u = Edge[i].to; cur[now] = i;
if (Edge[i].Flow && !vis[u] && dis[now] + Edge[i].val == dis[u])
{
LL Minn = dfs(u, Min(Flow - used, Edge[i].Flow));
if (Minn)
{
Edge[i].Flow -= Minn; Edge[i ^ 1].Flow += Minn; used += Minn;
ans_Spend += Edge[i].val * Minn; if (used == Minn) {vis[now] = 0; return used;}
}
}
}
vis[now] = 0;
if (used == 0) vis[now] = 1;
return used;
}
void dinic()
{
while (SPFA())
{
LL d; memset(vis, 0, sizeof(vis));
for (int i = 1; i <= n; ++i) cur[i] = Head[i];
while ((d = dfs(s, INF)) != 0) {ans_Flow += d;}
}
}
int main()
{
n = read(), m = read(), s = read(), t = read();
for (int i = 1; i <= m; ++i)
{
int u = read(), v = read(), w = read(), c = read();
add_Edge(u, v, w, c); add_Edge(v, u, 0, -c);
}
dinic(); printf("%lld %lld\n", ans_Flow, ans_Spend);
return 0;
}
dinic 求解费用流的思路就是将 BFS 分层换成 SPFA 求解最短路,然后照常 DFS 就好。