网络流-最小费用最大流

最小费用最大流 同时被 2 个专栏收录
3 篇文章 0 订阅

定义

有时,网络中每条边除了容量还有费用。最小费用最大流(MincostMaxflow,简称MCMF)就是在最大流的基础上,费用最小。

实现

和EK类似,最小费用最大流的求法也是暴力寻找增广路,但是不是用Bfs进行增广,而是用Bellman-Ford或者Spfa进行增广。寻找增广路时,先要满足可以增广,然后检查费用是否更优秀,两者都满足,才修正。这样增广下来,不难发现肯定满足费用最小。

这个算法的效率是玄学……但是实际运用中应该可能大概够了吧。

模板

实在没找到模板题,用POJ2135当模板题好了。
因为每条道路只能通过一次,所以我们可以认为每条道路的容量为1,费用为这条道路的长度(由于这道题是双向边,所以必须正反都建边)。但是要走回来怎么办?因为不能重复走,所以走回来就等价于走两次(已双向建边),我们可以增加两个虚拟节点0和n+1:0向1构造一条容量为2,费用为0的边,n向n+1构造一条容量为2,费用为0的边。然后直接刷最小费用最大流即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1000,maxm=40000;

int n,m,E,lnk[maxn+5],son[maxm+5],nxt[maxm+5];
int que[maxn+5],p[maxn+5],dis[maxn+5],MF[maxn+5];
bool vis[maxn+5];
struct Edge
{
    int fa,cap,flow,cost;
};
Edge e[maxm+5];

void Add(int x,int y,int a,int b)
{
    son[E]=y;nxt[E]=lnk[x];lnk[x]=E;e[E].fa=x;e[E].cap=a;e[E].flow=0;e[E].cost=b;E++;
    son[E]=x;nxt[E]=lnk[y];lnk[y]=E;e[E].fa=y;e[E].cap=0;e[E].flow=0;e[E].cost=-b;E++;
}
bool Spfa(int st,int gl,int &MIN,int &MAX)
{
    memset(vis,0,sizeof(vis));memset(dis,63,sizeof(dis));memset(MF,63,sizeof(MF));
    int Head=0,Tail=0,INF=dis[0];que[++Tail]=st;vis[st]=true;dis[st]=0;
    while (Head!=Tail)
    {
        int x=que[Head=(Head+1)%maxn];vis[x]=false;
        for (int j=lnk[x];~j;j=nxt[j])
            if (e[j].cap>e[j].flow&&dis[x]+e[j].cost<dis[son[j]]) //同时满足两个条件
            {
                dis[son[j]]=dis[x]+e[j].cost;p[son[j]]=j;
                MF[son[j]]=min(MF[x],e[j].cap-e[j].flow);
                if (!vis[son[j]])
                {
                    que[Tail=(Tail+1)%maxn]=son[j];vis[son[j]]=true;
                    if (dis[que[(Head+1)%maxn]]>dis[que[Tail]])
                        swap(que[(Head+1)%maxn],que[Tail]);
                }
            }
    }
    if (dis[gl]==INF) return false;
    MIN+=dis[gl]*MF[gl];MAX+=MF[gl];
    for (int now=gl;now!=st;now=e[p[now]].fa)
        e[p[now]].flow+=MF[gl],e[p[now]^1].flow-=MF[gl];
    return true;
}
int MCMF(int s,int t)
{
    int MIN=0,MAX=0;
    while (Spfa(s,t,MIN,MAX));
    return MIN;
}
int main()
{
    freopen("MCMF.in","r",stdin);
    freopen("MCMF.out","w",stdout);
    scanf("%d%d",&n,&m);memset(lnk,255,sizeof(lnk));
    Add(0,1,2,0);Add(n,n+1,2,0);
    for (int i=1;i<=m;i++)
    {
        int x,y,z;scanf("%d%d%d",&x,&y,&z);
        Add(x,y,1,z);Add(y,x,1,z);
    }
    printf("%d\n",MCMF(0,n+1));
    return 0;
}
  • 1
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值