spfa算法_算法学习笔记(31): 最小费用最大流

d2fafb6efa7c25b61317c79ae2a4e80d.png

我们现在来考虑比一般的网络流复杂一点的一个模型:最小费用最大流Minimum Cost Maximum Flow,MCMF)。

现在网络上的每条边,除了容量外,还有一个属性:单位费用。一条边上的费用等于流量×单位费用。我们知道,网络最大流往往可以用多种不同的方式达到,所以现在要求:在保持流最大的同时,找到总费用最少的一种。

如下图,有多种方式可以达到最大流3,但是S->3->T (2) + S->3->2->T (1)这种流法的费用是7×2+5×1=19,而S->3->T (2) + S->1->2->T (1)这种流法的费用则是7×2+4×1=18,后者比前者的费用更低。事实上,后者正是这个网络的最小费用最大流。

ea9457fdf530bd25ac9872585bb55f53.png

其实这个问题很好解决。我们已经知道,只要建了反向边,无论增广的顺序是怎样,都能求出最大流。所以我们只需要每次都增广费用最少的一条路径即可。具体地,把EK算法里的BFS换成SPFA:

int head[MAXN], cnt = 1;  // 这里特意把存图也贴出来,记住额外存一个参数c(费用)
struct Edge
{
    int to, w, c, next;
} edges[MAXM * 2];
inline void add(int from, int to, int w, int c)
{
    edges[++cnt].to = to;
    edges[cnt].w = w;
    edges[cnt].c = c;
    edges[cnt].next = head[from];
    head[from] = cnt;
}
int n, m, s, t, last[MAXN], flow[MAXN], inq[MAXN], dis[MAXN];
queue<int> Q;
bool SPFA()
{
    while (!Q.empty())
        Q.pop();
    memset(last, -1, sizeof(last));
    memset(inq, 0, sizeof(inq));
    memset(dis, 127, sizeof(dis));
    flow[s] = INF;
    dis[s] = 0;
    Q.push(s);
    while (!Q.empty())
    {
        int p = Q.front();
        Q.pop();
        inq[p] = 0;
        for (int eg = head[p]; eg != 0; eg = edges[eg].next)
        {
            int to = edges[eg].to, vol = edges[eg].w;
            if (vol > 0 && dis[to] > dis[p] + edges[eg].c) // 容量大于0才增广
            {
                last[to] = eg; // 记录上一条边
                flow[to] = min(flow[p], vol); // 更新下一个点的流量
                dis[to] = dis[p] + edges[eg].c; // 注意这里是c不是w
                if (!inq[to])
                {
                    Q.push(to);
                    inq[to] = 1;
                }
            }
        }
    }
    return last[t] != -1;
}
int maxflow, mincost;
inline void MCMF() // 这里要求两个值,直接改成给全局变量赋值了,传pair也可以吧
{
    while (SPFA())
    {
        maxflow += flow[t];
        mincost += dis[t] * flow[t];
        for (int i = t; i != s; i = edges[last[i] ^ 1].to)
        {
            edges[last[i]].w -= flow[t];
            edges[last[i] ^ 1].w += flow[t];
        }
    }
}

注意:建边的时候要这样建:

add(x, y, v, c);
add(y, x, 0, -c);

反向边的费用是正向边的相反数(只要你知道建反向边的目的,这应该是显然的)。

你会发现,其实这和一般的EK算法没多大区别(毕竟SPFA可以看作一种特殊的BFS)。网上好多文章先说“MCMF就是把dinic的BFS换成SPFA”,然后拍上来的代码明明就是把EK的BFS换成SPFA……我都怀疑他们是互相抄的(划掉)。其实如果是一般的dinic的话,在这个问题上并没有什么优势啊,毕竟多路增广和当前弧优化好像在MCMF问题上作用都不大?当然,经过某些特别的优化,应该还是能取得一些优势吧。

至于为什么用SPFA而不是Dij,主要是因为很多费用流模型都会涉及到负权边(包括洛谷模板题)。众所周知,SPFA的复杂度不稳定,不过,这个算法的复杂度本身就不稳定,还与流量有关,所以一般不用担心被卡。当然,经过特殊处理,还是可以跑Dij的,暂且不讲。


Pecco:算法学习笔记(目录)​zhuanlan.zhihu.com
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值