费用流:给定一个网络 G,每条边除了有容量限制 ,还有一个单位流量的费用
。当
的流量为
时,需要花费
的费用。(来源:OI-Wiki)
1、算法模板及简单应用(SSP算法)
还记得我们最大流所用到的第一个方法EK算法吗?EK算法的思路就是每次在残留网络中找到一条增广路增广。如果算上费用流的话我们只需要考虑每次找到一条费用最小的增广路即可。我们可以用数学归纳法证明:
命题:在每次找增广路时都找费用最小的增广路,最后求出的最大流一定是最小费用流
证明:设
是一条可行流,
为我们找到的最小费用增广路,|F|为最大流,
为最小费用流。
由于
,反证法我们找到的最大流不是最小费用流,
那么
,
则
,
也就是
,说明有一条费用更小的增广路,这与我们命题相矛盾
所以命题成立。
那么如何找到费用最小的增广路呢?
EK算法里我们是用bfs搜索在残留网络上找到一条从源点到汇点的增广路,这里的边对于bfs来说其实相当于没有边权,因为只有走和不走两种选择。而费用流就相当于边权,那么考虑最短路中带权的模型,我们只需要把EK算法中的bfs换成spfa即可。这种算法又叫SSP算法。
唯一的缺陷在于spfa无法处理有负权回路的情况,如果遇到带负权的情况,需要用到一个消圈定理(此处放个超链接)。
看一下模板题和代码(来源:Acwing):
给定一个包含 n 个点 m 条边的有向图,并给定每条边的容量和费用,边的容量非负。
图中可能存在重边和自环,保证费用不会存在负环。
求从 S 到 T 的最大流,以及在流量最大时的最小费用。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 5010, M = 100010, INF = 1e8;
int n, m, S, T;
int h[N], e[M], f[M], w[M], ne[M], idx;
int q[N], d[N], pre[N], incf[N];
bool st[N];
void add(int a, int b, int c, int d)
{
e[idx] = b, f[idx] = c, w[idx] = d, ne[idx] = h[a], h[a] = idx ++ ;
e[idx] = a, f[idx] = 0, w[idx] = -d, ne[idx] = h[b], h[b] = idx ++ ;
}
bool spfa()
{
int hh = 0, tt = 1;
memset(d, 0x3f, sizeof d);
memset(incf, 0, sizeof incf);
q[0] = S, d[S] = 0, incf[S] = INF;
while (hh != tt)
{
int t = q[hh ++ ];
if (hh == N) hh = 0;
st[t] = false;
for (int i = h[t]; ~i; i = ne[i])
{
int ver = e[i];
if (f[i] && d[ver] > d[t] + w[i])
{
d[ver] = d[t] + w[i];
pre[ver] = i;
incf[ver] = min(f[i], incf[t]);
if (!st[ver])
{
q[tt ++ ] = ver;
if (tt == N) tt = 0;
st[ver] = true;
}
}
}
}
return incf[T] > 0;
}
void EK(int& flow, int& cost)
{
flow = cost = 0;
while (spfa())
{
int t = incf[T];
flow += t, cost += t * d[T];
for (int i = T; i != S; i = e[pre[i] ^ 1])
{
f[pre[i]] -= t;
f[pre[i] ^ 1] += t;
}
}
}
int main()
{
scanf("%d%d%d%d", &n, &m, &S, &T);
memset(h, -1, sizeof h);
while (m -- )
{
int a, b, c, d;
scanf("%d%d%d%d", &a, &b, &c, &d);
add(a, b, c, d);
}
int flow, cost;
EK(flow, cost);
printf("%d %d\n", flow, cost);
return 0;
}
作者:yxc
链接:https://www.acwing.com/activity/content/code/content/457817/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
2、二分图最优匹配
二分图匹配问题我们在最小割一章已经证明过了,同样的在匹配的边上加上一个费用,改用费用流即可。
3、网格图匹配
看一条题目:
在一个 N×N 的矩形网格中,每个格子里都写着一个非负整数。
可以从左上角到右下角安排 K 条路线,每一步只能往下或往右,沿途经过的格子中的整数会被取走。
若多条路线重复经过一个格子,只取一次。
求能取得的整数的和最大是多少。
对于这类网格图模型,我们可以考虑将起点连接源点,将终点连接汇点,边权应当是我在图里走过的路径数K。那么对于图中的每一个格子我们都可以看做一个点,与之相连的是它可以走到的下一个点。
考虑图中点的性质:
1、能获得的收益(费用权)在点上,那么我们可以通过拆点将点分为入点和出点两部分,通过一条边连接,这样我们就可以把费用转移到边上了。
2、第一次走到当前点可以拿走当前点的数,之后再来什么都拿不到,那么我们可以再把入点和出点之间的边拆成两份,第一份的费用为格点数,但流量为1;第二份的费用为0,流量可以为INF。
如图所示:
当图建立完成之后我们再检验一遍最大流是否满足我们题目的含义即可。