• 网络流重制版:基于 Capacity Scaling 的弱多项式复杂度最小费用流算法


    前言

    Ouuan Orz

    当然,先说一下弱多项式是啥?

    OI 界中叫做 Dinic 和 EK 的那两个最大流算法,把其中的 BFS 改成求最短路,复杂度都是与值域多项式相关的,即复杂度是伪多项式的。

    多项式复杂度有弱多项式和强多项式两种,弱多项式就是关于输入长度( (n)(m) 之类的,以及 (log 值域))为多项式复杂度,强多项式就是在加减乘除为 (O(1)) 时复杂度关于数据规模为多项式(就是说跟值域完全无关,只和 (n, m) 之类的相关,复杂度关于 (n, m) 之类的为多项式)。

    当然,这时(Ouuan)说的。

    算法讲解

    要求

    1. (st)无入边,(ed)无出边。
    2. 可以有负环。

    无源汇最小费用流

    对于有源汇最小费用最大流的定义我们改一下:

    没有(st)(ed),同时最小化(sumlimits_{(i,j)∈E}cost(i,j)*f(i,j))

    当然,这个时候你可能会好奇这个我们讲的有源汇最小费用最大流有个(der)的关系?

    那如果我们从(ed)(st)连接一条无限小的边,使得流量越多越好,然后后面减掉就行了。

    做法

    首先,这个流是最小费用当且仅当图中不存在负环,证明和上篇重制版最小费用最大流博客雷同,不予以赘述。

    首先明白一个定理:如果把每条边容量乘(2),则对应流量乘(2),最小费用乘(2),因为乘(2)一定不存在负环。

    那么接下来就非常简单了,把边权拆成二进制,维护残余网络(G),一开始(G)中的容量和流量都为(0),然后二进制从高到低扫描,每一位把所有边的容量和流量乘(2),但是需要注意,有些边这一位二进制可能为(1),因此这条边会加入到残余网络中,这就非常的蛋疼了,好的方法是这条边是((x,y)),在加入前从(y)跑一遍最短路,如果(d[x]+cost(x,y)<0),那么就不加入,并且把(y)(x)的最短路的流量全部减一,当然,如果这条边原本就存在,则直接流量(+1)即可。

    至于为什么直接跑最短路即可,因为我们维护的残余网络中一定没有负环啊。

    时间复杂度:(O(nm^2log{U}))(U)是边中的最大流量。

    例题:https://uoj.ac/problem/487

    #include<cstdio>
    #include<cstring>
    #define  N  5100
    #define  M  110000
    using  namespace  std;
    typedef  long  long  LL;
    template<class  T>
    inline  T  mymin(T  x,T  y){return  x<y?x:y;}
    template<class  T>
    inline  T  mymax(T  x,T  y){return  x>y?x:y;}
    int  n,m;
    struct  node
    {
    	int  x,y,next;
    	LL  c/*表示它们现在现有的流量*/,d;
    }a[M];int  len=1,last[N];
    LL  cap[N];//表示它们原本的流量 
    inline  void  ins_node(int  x,int  y,LL  c,LL  d){len++;a[len].x=x;a[len].y=y;a[len].d=d;cap[len]=c;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);}
    //SPFA
    LL  d[N];
    int  list[N],head,tail,pre[N];
    bool  v[N],vv[N];
    void  spfa(int  st,int  ed)
    {
    	list[head=1]=st;tail=2;
    	memset(d,20,sizeof(d));d[st]=0;
    	memset(pre,0,sizeof(pre));
    	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(a[k].c>0  &&  d[y]>d[x]+a[k].d)
    			{
    				d[y]=d[x]+a[k].d;
    				pre[y]=k;
    				if(!v[y])
    				{
    					v[y]=1;
    					list[tail++]=y;
    					if(tail==n+1)tail=1;
    				}
    			}
    		}
    	}
    }
    LL  cost=0,ans=0,ffuck=0;
    void  trash(int  st,int  ed)
    {
    	LL  mmax=0/*,sum=0用来记录st-ed添加的那一条边应该是多少*/,summ=0;
    	for(int  i=2;i<=len;i++)
    	{
    //		if(a[i].d>0)sum+=a[i].d;
    		summ+=cap[i];
    		mmax=mymax(mmax,cap[i]);
    	}
    	ins(ed,st,summ,-(LL)999999999);
    	mmax=mymax(mmax,cap[len-1]);
    	int  l=1;
    	while(((LL)1<<l)-1<mmax)l++;
    	
    	for(int  ll=l;ll>=1;ll--)//从高位到低位开始扫描二进制 
    	{
    		cost<<=1;
    		LL  shit=((LL)1<<(ll-1));
    		memset(vv,0,sizeof(vv));
    		for(int  i=2;i<=len;i++)
    		{
    			a[i].c<<=1;
    			if(a[i].c)a[i].c+=(cap[i]&shit)>0,vv[i]=1;
    		}
    		//对于所有已经存在的边不用扫描 
    		for(int  i=2;i<=len;i++)
    		{
    			if(vv[i])continue;
    			if(cap[i]&shit)//反向边绝对不会进来 
    			{
    				int  x=a[i].x,y=a[i].y;
    				spfa(y,x);
    				if(d[x]+a[i].d<0/*负环!!!!*/)
    				{
    					cost+=a[i].d;
    					x=pre[x];
    					while(x)
    					{
    						cost+=a[x].d;
    						a[x].c--;a[x^1].c++;
    						x=pre[a[x].x];
    					}
    					a[i^1].c++;
    				}
    				else  a[i].c++;
    			}
    		}
    	}
    	for(int  k=last[st];k;k=a[k].next)
    	{
    		if(!(k&1))ans+=cap[k]-a[k].c;
    	}
    	printf("%lld %lld
    ",ans,cost-(cap[len-1]-a[len-1].c)*a[len-1].d+ffuck);
    }
    int  main()
    {
    	int  st,ed;
    	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);
    		if(x==y)
    		{
    			if(d<0)ffuck+=c*d;//自环 
    		}
    		else  ins(x,y,c,d);
    	}
    	trash(st,ed);
    	return  0;
    }
    

    细节

    无限小?

    边权?

    总结

    放心,因为比赛一般都是构图题,难以卡掉(ZKW)(EK),所以,大胆的,放心的用(ZKW)吧,学这个算法就图一乐。

    参考资料

    洛谷的讨论

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

    要准备NOIP啦,赛后补充Dij的做法,还有亿点细节补充。

    代码也要补点注释。

    赛前还是直接打个简单思路就去准备比赛了。

  • 相关阅读:
    inMap 经纬度 全国 全球
    SpringCloud Stream 使用
    beta阶段贡献分配实施
    20181113-3 Beta阶段贡献分配规则
    作业 20181120-3 Beta发布
    β发布:文案+美工展示博客
    Scrum立会报告+燃尽图(十一月二十七日总第三十五次):β阶段最后完善
    Beta发布——视频博客
    Scrum立会报告+燃尽图(十一月二十六日总第三十四次):上传β阶段展示视频
    Scrum立会报告+燃尽图(十一月二十五日总第三十三次):展示博客
  • 原文地址:https://www.cnblogs.com/zhangjianjunab/p/14039241.html
Copyright © 2020-2023  润新知