网络流重制版:最小费用最大流以及其正确性,还有各种算法的个人SB分析

本文探讨了在网络流中引入费用的概念,详细介绍了最小费用最大流问题,包括其定义、增广路径的选择策略。文章讲解了MCMF算法、ZKW费用流算法和原始对偶算法,讨论了算法的细节、性质和正确性证明,并提供了参考资料和一些需要注意的陷阱。
摘要由CSDN通过智能技术生成

费用流的定义

有没有考虑过,如果一条边还有费用呢???

就像带权二分图匹配那样子。

给出定义, c o s t ( i , j ) cost(i,j) cost(i,j)为这条弧的花费。

那么不仅要在最大化流量的同时(优先级最高),最小化 c o s t ( i , j ) ∗ f ( i , j ) cost(i,j)*f(i,j) cost(i,j)f(i,j)

可以发现,如果图外面存在一个负环,那么这个负环会有流量,且会影响答案。

请注意:最小费用流没有严格要求流最大,所以本篇文章讲的是最小费用最大流。

大概做法

首先,依旧是找增广路,但是呢,挑选依据不同了,改成了以最小费用的路径为挑选依据了(可以证明这样的挑选方法是 100 100 100%亿是正确的,可以跑到最小费用)。

同时呢,反向边的定义也要改一下了,既然你经过反向边的时候流量会被消除,那么费用是不是也要取负?

当然,这样会丢失层数一个非常重要的性质,就是如果一条路径经过另外一条路径的反向边,他们交换,使得互相不干扰,并不会改变长度和,但是如果是层数,交换少了两条边,会少 2 2 2

所以,正反向边的费用和为 0 0 0,因此,费用流随随便便就会出现 0 0 0环的情况。

事实上,一般情况下,网络流的建图要求刚开始的时候不存在负环(可以证明这种情况在后面增广的时候也同样不存在负环)。

当然,不用担心,这些证明在后面都会补上的。

算法讲解

讲到这里,你应该默认每次最小费用就是对的了(证明往后翻)。

MCMF算法

非常的简单粗暴,直接用 S P F A SPFA SPFA增广就行了( D i j k s t r a Dijkstra Dijkstra不行,因为中间可能存在负环)。

时间复杂度: O ( n m f ) O(nmf) O(nmf) f f f为流量)

#include<cstdio>
#include<cstring>
#define  N  5100
#define  M  1100000 
using  namespace  std;
typedef  long  long  ll;
struct  node
{
   
    int  y,next,other;ll  c,k;
}a[M];int  last[N],len,n,m,st,ed;
int  qian[N],b[N],list[N],head=1,tail=2;
ll  flow[N],dis[N];
bool  v[N];
ll  zans=0,cost=0;
inline  ll  mymin(ll  x,ll  y){
   return  x<y?x:y;}
inline  void  ins(int  x,int  y,ll  c,ll  k)
{
   
    len++;
    a[len].y=y;a[len].c=c;a[len].k=k;
    a[len].next=last[x];last[x]=len;
    len++;
    a[len].y=x;a[len].c=0;a[len].k=-k;
    a[len].next=last[y];last[y]=len;
    a[len].other=len-1;
    a[len-1].other=len;
}
inline  bool  spfa()
{
   
    memset(v,false,sizeof(v));v[st]=true;
    head=1;tail=2;list[1]=st;
    memset(dis,63,sizeof(dis));dis[st]=0;
    b[ed]=-1;
    while(head!=tail)
    {
   
        int  x=list[head];
        for(register  int  k=last[x];k;k=a[k].next)
        {
   
            int  y=a[k].y;
            if(a[k].c>0  &&  dis[x]+a[k].k<dis[y])
            {
   
                dis[y]=dis[x]+a[k].k;
                flow[y]=mymin(a[k].c,flow[x]);
                qian[y]=x;b[y]=k;
                if(v[y]==false)
                {
   
                    v[y]=true;
                    if(dis[list[head+1]]>dis[y])
                    {
   
                        int  lpl=head-1;
                        if(lpl==0)lpl=n;
                        list[lpl]=list[head];
                        list[head]=y;head=lpl;
                    }
                    else
                    {
   
                        list[tail]=y;
                        tail++;
                        if(tail==n+1)tail=1;
                    }
                }
            }
        }
        head++;
        if(head==n+1)head=1;
        v[x]=false;
    }
    if(b[ed]!=-1)//找到增广路径
    {
   
        int  y=ed,root=0;
        while(y!=st)
        {
   
            root=b[y];y=qian[y];
            a[root].c-=flow[ed];
            a[a[root].other].c+=flow[ed];
        }
        zans+=flow[ed];
        cost+=flow[ed]*dis[ed];
    }
    return  b[ed]!=-1;
}
int  main()
{
   
    scanf("%d%d%d%d",&n,&m,&st,&ed);
    for(register  int  i=1;i<=m;i++)
    {
   
        int  x,y;
        ll  z,k;
        scanf("%d%d%lld%lld",&x,&y,&z,&k);
        ins(x,y,z,k);
    }
    flow[st]=ll(999999999999999);
    while(spfa()==true);
    printf("%lld %lld",zans,cost);
    return  0;
}

ZKW费用流

这个有个非常有意思的故事:相传是 Z K W ZKW ZKW神在赛场上遇到费用流的题目脑补了这个算法,但是怕错没打,后来出来实现了一下发现可以!!!

具体你会发现 M C M F MCMF MCMF其实其最短路是非严格递增的,所以可以一次性直接把相同长度的最短路一次性跑完,简单来说就是像 D i n i c Dinic Dinic E K EK EK一样,然后码一下即可。

需要注意的是用 v v v数组保存一下这个点有没有被走过。

  1. 因为最短路中如果存在 0 0 0环不用 v v v数组记录这个点在不在路径上可能会陷入死循环。
  2. 因为不存在负环,所以在负环上浪费时间是非常没有意义的。

时间复杂度依旧是丑陋的 O ( n m f ) O(nmf) O(nmf)

当然,我的 Z K W ZKW ZKW的写法和常人不同。

#include<cstdio>
#include<cstring>
using  namespace  std;
typedef  long  long  ll;
struct  node
{
   
    int  y,next,other;
    ll  c,k;
}a[201000];int  last[5100],len;
long  long  d[5100];
bool  v[5100];
int  n,m,st,ed;
ll  cost=0;
inline void  ins(int  x,int  y,ll  c,ll  k)
{
   
    len++;
    a[len].y=y;a[len].c=c;a[len].k=k;
    a[len].next=last[x];last[x]=len;
    len++;
    a[len].y=x;a[len].c=0;a[len].k=-k;
    a[len].next=last[y];last[y]=len;
    a[len-1].other=len;
    a[len].other=len-1;
}
int  list[5100],head,tail;/*队列*/
inline  bool  spfa()
{
   
    memset(v,false,sizeof(v));v[ed]=true;/*判断是否进入队列*/
    memset(d,-1,sizeof(d));d[ed]=0;/*从终点到这里要多少费用*/
    head=1;tail=2;list[head]=ed;/*从终点出发*/
    while(head!=tail)
    {
   
        int  x=list[head];
        for(int  k=last[x];k;k=a[k].next)
        {
   
            if<
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值