【网络流自学Ⅱ】最小费用最大流

最小费用最大流也是网络流中的经典问题,其实就是在最大流问题的基础之上加了一些限制:对于某条管道i,传输单位流量需要ci的费用,在这样一个网络中求费用最少的最大流。
可以把每条管道上的费用ci看作边长,这样求最小费用等价于求最短路,考虑在EK算法中有一个求增广路的bfs,只要把bfs换成spfa,求最短(其实是总费用最小)增广路,累加增逛的流量flow就能求出最大流,而统计费用时只需累加flow与这条增广路的长度(费用)的乘积。
题目:洛谷P3381
code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <cstdlib>
#include <queue>
#include <map>
#define ll long long
using namespace std;
const int maxm=5e4+5;
const int maxn=5005;
const int inf=0x3f3f3f3f;
int getnum()
{
    int num=0;
    bool f=1;
    char c=getchar();
    while (!isdigit(c))
    {
        if(c=='-')f=0;
        c=getchar();
    }
    while(isdigit(c))
    {
        num=num*10+c-'0';
        c=getchar();
    }
    return f ? num : -num;
}
int p=1,h[maxn],nxt[maxm<<1],w[maxm<<1],c[maxm<<1],v[maxm<<1],disc[maxn],minf[maxn];
bool f[maxn];
int preid[maxn],pre[maxn];
int n,m,s,t,mincost,maxflow;
void add(int x,int y,int z,int cos)
{
    ++p;
    v[p]=y;
    w[p]=z;
    c[p]=cos;
    nxt[p]=h[x];
    h[x]=p;
}
bool spfa(int s,int t)
{
    queue<int>q;
    memset(disc,0x3f, sizeof(disc));
    memset(f,0, sizeof(f));
    memset(pre,0, sizeof(pre));
    memset(preid,0, sizeof(preid));
    memset(minf,0x3f, sizeof(minf));
    q.push(s);
    disc[s]=0;
    f[s]=1;
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        f[x]=0;
        for(int i=h[x];i;i=nxt[i])
        {
            int to=v[i];
            if(w[i]&&(disc[x]+c[i]<disc[to]))
            {
                disc[to]=disc[x]+c[i];
                pre[to]=i;
                preid[to]=x;
                minf[to]=min(minf[x],w[i]);
                if(!f[to])
                {
                    q.push(to);
                    f[to]=1;
                }
            }
        }
    }
    return disc[t]!=inf;
}
void MCMF(int s,int t)
{
    while (spfa(s,t))
    {
        for(int i=t;i!=s;i=preid[i])
        {
            w[pre[i]]-=minf[t];
            w[pre[i]^1]+=minf[t];
        }
        maxflow+=minf[t];
        mincost+=minf[t]*disc[t];//累加求总费用
    }
    return ;
}
int main()
{
    n=getnum(),m=getnum(),s=getnum(),t=getnum();
    for(int i=1;i<=m;i++)
    {
        int x=getnum(),y=getnum(),z=getnum(),cos=getnum();
        add(x,y,z,cos);
        add(y,x,0,-cos);
    }
    MCMF(s,t);
    printf("%d %d",maxflow,mincost);
    return 0;
}

近年来关于spfa算法有一个著名的结论就是它死了,所以这里我们可以换另一种更稳定的方法:dijkstra
不过dijkstra相比于spfa来说有一个弱点就是怕负环图,所以这里用到了一个类似离散化的思想,就是把最短路加上一个很大的数(前提是保证不影响答案),在统计费用时减去这个极大的值就可以了。
而且我们可以用“上一次求出的最短路”来作为这个极大的值(过两天大概会 更新对于这个做法的说明)
code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <cstdlib>
#include <queue>
#include <map>
#define ll long long
using namespace std;
const int maxm=5e4+5;
const int maxn=5005;
const int inf=0x7fffffff;
int getnum()
{
    int num=0;
    bool f=1;
    char c=getchar();
    while (!isdigit(c))
    {
        if(c=='-')f=0;
        c=getchar();
    }
    while(isdigit(c))
    {
        num=num*10+c-'0';
        c=getchar();
    }
    return f ? num : -num;
}
int p=1,h[maxn],nxt[maxm<<1],w[maxm<<1],c[maxm<<1],v[maxm<<1],minf[maxn];
ll disc[maxn],height[maxn];
bool f[maxn];
int preid[maxn],pre[maxn];
int n,m,s,t,mincost,maxflow;
void add(int x,int y,int z,int cos)
{
    ++p;
    v[p]=y;
    w[p]=z;
    c[p]=cos;
    nxt[p]=h[x];
    h[x]=p;
}
struct node
{
    ll sumcost;
    int id;
    bool operator < (const node&a)const {
        return sumcost>a.sumcost;
    }
}nd[maxn];
bool dijkstra(int s,int t)
{
    priority_queue<node>q;//优先队列优化
    for(int i=1;i<=n;i++)
    {
        disc[i]=inf;
        f[i]=0;
        minf[i]=inf;
    }
    q.push((node){0,s});
    disc[s]=0;
    while(!q.empty())
    {
        node x=q.top();
        q.pop();
        if(f[x.id])continue;
        f[x.id]=1;
        for(int i=h[x.id];i;i=nxt[i])
        {
            int to=v[i];
            if((w[i]>0)&&(disc[x.id]+c[i]+height[x.id]-height[to]<disc[to]))
            {
                disc[to]=disc[x.id]+c[i]+height[x.id]-height[to];
                pre[to]=i;
                preid[to]=x.id;
                minf[to]=min(minf[x.id],w[i]);
                q.push((node){disc[to],to});
            }
        }
    }
    return disc[t]!=inf;
}
void MCMF(int s,int t)
{
    while (dijkstra(s,t))
    {
        for(int i=t;i!=s;i=preid[i])
        {
            w[pre[i]]-=minf[t];
            w[pre[i]^1]+=minf[t];
        }
        maxflow+=minf[t];
        mincost+=minf[t]*(disc[t]-height[s]+height[t]);
        for(int i=1;i<=n;i++)
        {
            height[i]+=disc[i];
        }
    }
    return ;
}
int main()
{
    n=getnum(),m=getnum(),s=getnum(),t=getnum();
    for(int i=1;i<=m;i++)
    {
        int x=getnum(),y=getnum(),z=getnum(),cos=getnum();
        add(x,y,z,cos);
        add(y,x,0,-cos);
    }
    MCMF(s,t);
    printf("%d %d",maxflow,mincost);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值