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

文章目录

  • 费用流的定义
  • 大概做法
  • 算法讲解
    • MCMF算法
    • ZKW费用流
    • 原始对偶算法
  • 细节、性质以及证明
    • s<f优化
    • 当流量为i时,且图中不存在负环,那么此时是流量为i时最小费用
    • 关于最短路增广正确性证明
    • 增广过程中不存在负环
    • 关于最短路非严格单调递增
    • ZKW只要在合法网络中的路径都会被增广
    • 关于ZKW最短路严格递增性质
  • 参考资料

费用流的定义

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

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

给出定义, 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
#include
#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
#include
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(a[a[k].other].c>0/*由于是倒着搜的,所以边也要反向边*/  &&  (a[a[k].other].k+d[x]<d[a[k].y]  ||  d[a[k].y]==-1))/*判断边是否可行并更新*/
            {
                d[a[k].y]=a[a[k].other].k+d[x];/*更新*/
                int  y=a[k].y;
                if(v[y]==false)
                {
                    v[y]=true;
                    list[tail]=y;
                    tail++;
                    if(tail==n+1)tail=1;
                }
            }
        }
        head++;
        if(head==n+1)head=1;
        v[x]=false;
    }
    return  d[st]!=-1;/*返回bool值*/
}
inline  ll  mymin(ll  x,ll  y){return  x<y?x:y;}/*找最小值*/
long  long  find(int  x,ll  f)
{
    v[x]=true;
    if(x==ed){v[x]=false;return  f;}
    ll  ans=0,t=0;
    for(int  k=last[x];k;k=a[k].next)
    {
        int  y=a[k].y;
        if(v[y]==false/*这个点没走过才可以走,否则更新边的流量是会Balabala*/  &&  a[k].c>0  &&  d[x]-a[k].k==d[y]/*类似分层的操作*/  &&  ans<f)
        {
            ans+=t=find(y,mymin(a[k].c,f-ans));/*是不是很眼熟?*/
            a[k].c-=t;a[a[k].other].c+=t;cost+=t*a[k].k;
        }
    }
    if(ans==f)v[x]=false;//这个地方一定要加这个优化,原因和最大流类似
    return  ans;
}
int  main()
{
    scanf("%d%d%d%d",&n,&m,&st,&ed);
    for(int  i=1;i<=m;i++)
    {
        int  x,y;
        ll  z,l;
        scanf("%d%d%lld%lld",&x,&y,&z,&l);
        ins(x,y,z,l);
    }
    ll  zans=0;
    while(spfa()==true)/*建图完成!*/
    {
        zans+=find(st,ll(999999999999999));/*多次查找,找出所有增光路哦*/
    }
    printf("%lld %lld",zans,cost);
    return  0;
}

原始对偶算法

这个名字真的神奇,不用管这么高大上的算法。

实际上就是用某种神奇的方法,使得 D i j k s t r a Dijkstra Dijkstra可以在费用流上跑。

但是不要期望其可以在普通的最短路上用,因为其先决条件是跑一遍其余的最短路。。。

首先,对于每个点,我们给其标一个势能 p p p,然后把边 ( i , j ) (i,j) (i,j) C o s t Cost Cost设定为 p ( i ) + c o s t ( i , j ) − p ( j ) p(i)+cost(i,j)-p(j) p(i)+cost(i,j)p(j),同时图中用 C o s t Cost Cost作为费用,而一条 s t st st e d ed ed最短路就是原本的最短路减去 p ( e d ) p(ed) p(ed)

具体可以看一下证明,对于一条路径,我们将其点标号为 1 , 2 , 3 , . . . , k 1,2,3,...,k 1,2,3,...,k

那么就是 p ( 1 ) + c o s t ( 1 , 2 ) − p ( 2 ) + p ( 2 ) + c o s t ( 2 , 3 ) − p ( 3 ) + . . . − p ( k ) = d [ e d ] + p ( e d ) p(1)+cost(1,2)-p(2)+p(2)+cost(2,3)-p(3)+...-p(k)=d[ed]+p(ed) p(1)+cost(1,2)p(2)+p(2)+cost(2,3)p(3)+...p(k)=d[ed]+p(ed)

于是就有神犇发现了, p p p数组变成 d d d数组就可以满足新添加的边都是非负的。

因为有以下性质:
对于边 ( i , j ) (i,j) (i,j) d i s [ i ] + c o s t ( i , j ) ≥ d i s [ j ] dis[i]+cost(i,j)≥dis[j] dis[i]+cost(i,j)dis[j],即 d i s [ i ] + c o s t ( i , j ) − d i s [ j ] ≥ 0 dis[i]+cost(i,j)-dis[j]≥0 dis[i]+cost(i,j)dis[j]0

但是关键是,在增广完之后,不是会有些边增广掉了,导致这些边的 d i s dis dis在下次改变了,那岂不是每次跑完增广,就要重新 S P F A SPFA SPFA

不,其实这次增广完之后,直接用增广前的 d i s dis dis作势能就行了。

为什么?

首先,这条边 ( i , j ) (i,j) (i,j)能被走仅当 d i s [ i ] + c o s t ( i , j ) = d i s [ j ] dis[i]+cost(i,j)=dis[j] dis[i]+cost(i,j)=dis[j],所以如果这条边被增广,那么新的 ( j , i ) (j,i) (j,i)的费用为: d i s [ j ] − c o s t ( i , j ) = d i s [ i ] dis[j]-cost(i,j)=dis[i] dis[j]cost(i,j)=dis[i],所以就是 0 0 0,而如果没有被走过,显然成立。

时间复杂度: O ( m l o g n f ) O(mlognf) O(mlognf)

#include
#include
#include
#define  N  5100
#define  M  110000
using  namespace  std;
typedef  long  long  LL;
typedef  pair<LL,int> PII;
template<class  T>
inline  T  mymin(T  x,T  y){return  x<y?x:y;}
int  n,m;
struct  node
{
	int  y,next;
	LL  c,d;
}a[M];int  len=1,last[N];
inline  void  ins_node(int  x,int  y,LL  c,LL  d){len++;a[len].y=y;a[len].c=c;a[len].d=d;a[len].next=last[x];last[x]=len;}
inline  void  ins(int  x,int  y,LL  c,LL  d){ins_node(x,y,c,d);ins_node(y,x,0,-d);}
int  st,ed;
//dij 
LL  d[N],p[N]/*势能函数*/;
bool  v[N];//是否访问过 
priority_queue<PII,vector<PII>,greater<PII> >fuck;
inline  bool  DIJ()
{
	while(!fuck.empty())fuck.pop();
	memset(d,20,sizeof(d));d[st]=0;
	memset(v,0,sizeof(v));
	fuck.push(make_pair(0,st));
	while(!fuck.empty())
	{
		PII  id=fuck.top();fuck.pop();
		if(v[id.second])continue;
		v[id.second]=1;
		int  x=id.second;
		for(int  k=last[x];k;k=a[k].next)
		{
			int  y=a[k].y;
			if(!v[y]  &&  d[y]>d[x]+a[k].d+p[x]-p[y]  &&  a[k].c>0)
			{
				d[y]=d[x]+a[k].d+p[x]-p[y];
				fuck.push(make_pair(d[y],y));
			}
		}
	}
	return  v[ed];
}
int  list[N],head,tail;
bool  spfa()
{
	memset(d,20,sizeof(d));d[st]=0;
	list[head=1]=st;tail=2;
	memset(v,0,sizeof(v));v[st]=1;
	while(head!=tail)
	{
		int  x=list[head++];if(head==n+1)head=1;
		v[x]=0;
		for(int  k=last[x];k;k=a[k].next)
		{
			int  y=a[k].y;
			if(d[y]>d[x]+a[k].d  &&  a[k].c>0)
			{
				d[y]=d[x]+a[k].d;
				if(!v[y])
				{
					list[tail++]=y;if(tail==n+1)tail=1;
					v[y]=1;
				}
			}
		}
	}
	return  d[ed]!=d[0];
}
LL  cost=0;
LL  dfs(int  x,LL  f)
{
	if(x==ed)return  f;
	v[x]=1;
	LL  s=0,t;
	for(int  k=last[x];k;k=a[k].next)
	{
		int  y=a[k].y;
		if(a[k].c  &&  d[y]==d[x]+a[k].d+p[x]-p[y]  &&  !v[y])
		{
			s+=t=dfs(y,mymin(f-s,a[k].c));
			a[k].c-=t;a[k^1].c+=t;
			cost+=a[k].d*t;
			if(s==f){v[x]=0;return  s;}
		}
	}
	return  s;
}
int  main()
{
	scanf("%d%d%d%d",&n,&m,&st,&ed);
	for(int  i=1;i<=m;i++)
	{
		int  x,y;LL  c,d;
		scanf("%d%d%lld%lld",&x,&y,&c,&d);
		ins(x,y,c,d);
	}
	LL  ans=0;
	if(spfa())
	{
		do
		{
			memset(v,0,sizeof(v));
			ans+=dfs(st,LL(99999999999999));
			for(int  i=1;i<=n;i++)p[i]+=d[i];
		}while(DIJ());
	}
	printf("%lld %lld\n",ans,cost);
	return  0;
} 

细节、性质以及证明

s<f优化

可以发现,对于 v [ x ] v[x] v[x],如果 s ≠ f s≠f s=f其是不会还原变回 0 0 0的,这个优化在一般性质证明中默认没有,因为这个优化可能会影响性质的正确性。

为什么需要这个优化?

理由和 D i n i c Dinic Dinic一个道理,类似的数据就可以卡死。

当流量为i时,且图中不存在负环,那么此时是流量为i时最小费用

必要性:很明显,非常的明显,因为负环的话直接在这个环上增加 1 1 1,绝对不会改变 ∑ ( s t , i ) ∈ E f ( s t , i ) \sum\limits_{(st,i)∈E}f(st,i) (st,i)Ef(st,i)

充分性:如果存在一个更小费用的 f i ′ f_{i}' fi,用 f i ′ − f i f_{i}'-f_{i} fifi得到一个网络(对于一条边 ( i , j ) ∈ E (i,j)∈E (i,j)E,这条边在网络中的容量为左边的 f f f减去右边的 f f f,如果小于 0 0 0,则方向取反且取负,其他边不允考虑,基本上在证明的过程中都是不怎么考虑除 E E E以外的边的),这个网络是 G f i G_{f_{i}} Gfi的一个残余网络,这个网络肯定存在负环,因为 c o s t f i ′ < c o s t f i cost_{f_{i}'}costfi<costfi

关于最短路增广正确性证明

注意这里使用的是 M C M F MCMF MCMF

设第 i − 1 i-1 i1次增广后的流为 f 1 f_1 f1,然后第 i i i次增广为 f 2 f_2 f2,但是存在一个流 ∣ f 3 ∣ = ∣ f 2 ∣ , c o s t f 3 < c o s t f 2 |f_{3}|=|f_{2}|,cost_{f_3}f3=f2,costf3<costf2

这个时候,我们用上篇网络流类似的证明方法:

尝试用归纳法证明,在 f 1 f_{1} f1绝对是最小费用的情况下:

f 2 − f 1 f_2-f_1 f2f1得到一个网络,这个网络是残余网络 G f 1 G_{f_{1}} Gf1的子图,这个网络中只存在一条 s t st st e d ed ed的增广路径 p 1 p_1 p1,现在用类似的方法, f 3 − f 1 f_3-f_1 f3f1,得到的图便是一条增广路径 p 2 p_2 p2和一坨圈,因为 p 2 p_2 p2的费用≥ p 1 p_1 p1的费用,所以这些环中一定有负环,所以 f 1 f_{1} f1绝对不是最小费用流,矛盾,证毕。(很明显原图的 f f f,流量为 0 0 0 c o s t = 0 cost=0 cost=0,所以一定是最小费用流)

增广过程中不存在负环

由上面的证明就可以得到。

当然,非要特别特别特别严谨的证明,还有一种方法,就是考虑负环的生成条件,然后考虑最后一条经过这个负环的增广路,然后证明这条增广路存在更短的情况即可。

关于最短路非严格单调递增

这个其实有个非常简单的方法:

跟上次证明 h h h数组一个套路,同样开始证明 d d d数组。

这两玩意有个非常相同的地方,对于 d [ x ] d[x] d[x],如果其实从 d [ y ] d[y] d[y]继承来的,那么 s t st st y y y的最短路其实在 s t st st x x x的最短路的上,而 h h h也是。

所以,考虑第 i i i次增广时, d d d数组变成了 d ′ d' d数组,然后设 x x x为第一个 d ′ [ x ] < d [ x ] d'[x]d[x]<d[x]的位置,这里的第一个指的是在增广后的图中对于 s t st st x x x的最短路中,存在一条路径上的所有的点 i i i都满足 d [ i ] ′ ≥ d [ i ] d[i]'≥d[i] d[i]d[i]

考虑这条最短路为 s t ⇝ y → x st⇝y→x styx

如果 ( y , x ) (y,x) (y,x)这条边增广前就存在,那么成立。

如果 ( y , x ) (y,x) (y,x)这条边是在增广时增加的,那么:
( x , y ) (x,y) (x,y)是上次的增广路,设边费用为 k k k,有:
d [ x ] + k = d [ y ] , d [ x ′ ] − ( − k ) = d [ y ] ′ ≥ d [ y ] ≥ d [ x ] + k d[x]+k=d[y],d[x']-(-k)=d[y]'≥d[y]≥d[x]+k d[x]+k=d[y],d[x](k)=d[y]d[y]d[x]+k

矛盾,所以不成立。

所以 M C M F MCMF MCMF算法是可以非严格递增的,记得有道省选题就用到了这个性质。

ZKW只要在合法网络中的路径都会被增广

首先,把 s < f ss<f优化去掉,这个优化可能影响正确性。

什么叫合法网络?

对于 ( i , j ) (i,j) (i,j),如果 d i s [ i ] + c o s t ( i , j ) = d i s [ j ] dis[i]+cost(i,j)=dis[j] dis[i]+cost(i,j)=dis[j],那么这条边计入合法网络。

当然,如果在讨论中其 f − c = 0 f-c=0 fc=0,那么这条边认为是无意义的,不予讨论。

不难发现,一条边和其反向弧都在这个网络当中,所以这个网络应当在把 0 0 0环缩成一个点时,其应该是个有向无环图。

引理:任何一种增广方案都一定可以转化成一种在合法网络中就能增广的、互不干扰(即不走互相反向边)的方案。

解释一下,什么叫在合法网络中就能增广,即在增广一条边时不在其对应的反向边加上对应的流量,也能完整的把这个流量跑完。

实际上这两个形容词描述的是同个东西。(应该)

证明也非常简单,把这个合法网络看成一个普通的网络(正向弧就是这个网络中有意义的边),然后跑出容量网络,根据上次最大流的一个性质分解路径即可。

说完了引理,讲讲这个的证明:
假如合法网络 D F S DFS DFS之后还剩增广路径 p p p,如果其走过了别的增广路径的反向边,则交换,最终对应在原图上。
对于增广路径 p p p,如果其在被 D F S DFS DFS访问之前,这条路径就被一条路径 q q q经过了其的边(这里的经过必须满流才叫经过,因为只有满流才有影响)了,那么,我们就通过反向边,改变一下 p p p即可,但是怎么证明改变后的 p p p依旧满足能被访问到?步骤如下:

  1. 对于路径 p p p,拆为路径: s t − > x − > . . . − > e d st->x->...->ed st>x>...>ed,如果 x x x没有作为 s t st st的后继点访问过,则如果 q q q经过的边肯定不是 s t − > x st->x st>x(因为没有负环),则路径 p p p依旧满足没有被访问过(且因为流量大于 0 0 0,未来会被访问到)。
  2. 如果 x x x被访问到了,那么其在 D F S DFS DFS栈中,那么设路径中其后继点为 y y y,把路径改为: s t − > y − > . . . − > e d st->y->...->ed st>y>...>ed(认为 − > x − > ->x-> >x>是一条边)。

证毕。

当然,通过反向边改变一下路径这句话非常的神奇。

注意: p p p是通过反向边交换回来的,那么肯定也有方法换回去:

网络流重制版:最小费用最大流以及其正确性,还有各种算法的个人SB分析_第1张图片
因为我们其实就是重新模拟一遍 D F S DFS DFS罢了。

关于ZKW最短路严格递增性质

Z K W ZKW ZKW最神奇的事情是什么?

反向边可以在这一次就投入作用, d i s [ x ] + c o s t ( x , y ) = d i s [ y ] dis[x]+cost(x,y)=dis[y] dis[x]+cost(x,y)=dis[y] d i s [ y ] + ( − c o s t ( x , y ) ) = d i s [ x ] dis[y]+(-cost(x,y))=dis[x] dis[y]+(cost(x,y))=dis[x],可以发现,这两个是等价的,所以说, Z K W ZKW ZKW的反向边可以在 D F S DFS DFS的时候就直接投入战斗。

首先,对于一条增广路 p p p,第 i i i次增广后, p p p是同样的长度但没有被增广,如果 p p p走了第 i i i D F S DFS DFS的增广路径的反向边,那么交换。

设路径上 x x x s t st st的长度为 d [ x ] d[x] d[x],设原图中的最短路为 d [ x ] ′ d[x]' d[x],如果 d [ x ] > d [ x ] ′ d[x]>d[x]' d[x]>d[x],那么可以把原图中到 x x x的最短路和增广路中 x x x e d ed ed的路径合在一起,如果存在环,直接去掉,费用绝对会更加优秀,这与前面的性质相矛盾。

所以这条路径在合法网络中。

证毕。

参考资料

知乎的讨论

min25king卡飞

def mcmf_worst_instance(k):
  inf = 5 * 2 ** k // 4
  print("%d %d" % (2 * k + 2, k * (k + 1) + 1))
  for i in range(k):
    print("%d %d %d %d" % (1, i + 2, [1, 3][i] if i < 2 else 5 << (i - 2), 0))
  print("%d %d %d %d" % (2, k + 2, inf, 0))
  for i in range(k):
    for j in range(k):
      if i == j: continue
      print("%d %d %d %d" % (i + 2, k + j + 2, inf, (1 << max(i, j)) - 1))
  for i in range(k):
    print("%d %d %d %d" % (i + 2 + k, 2 * k + 2, 2 if i < 2 else 5 << (i - 2), 0))

mcmf_worst_instance(17) # |V| = 36

洛谷的讨论

弱多项式复杂度算法非常非常好的常考资料,真的非常非常好

最小费用最大流正确性证明

网络单纯形的参考资料,但是听说还是指数级的复杂度,没什么用,还挺难懂的

参考资料

ZKW亲手写的博客

注意:这是给我自己看的,读者应该看不懂,看懂且有证明的私信我。

证明s

伪证
如果一个点的s

ZKW的每个环节跑到的费用相同

从终点跑和起点跑的时间不同之处(从ed开始跑最短路只要存在0环即可,如果一开始不存在,那么后面也可以存在(共享0环))

原始对偶同样可能再构造的时候存在0环,不管是天然就有的,还是后来居上的,卡法(即不加 s < f ss<f优化的卡法)。

弧优化

你可能感兴趣的:(网络流重制版:最小费用最大流以及其正确性,还有各种算法的个人SB分析)