• 上下界网络流


    前置芝士

    最大流入门

    首先要比较清楚地了解网络最大流的原理以及(dinic)算法求最大流是过程

    大力推荐博客liu_runda nb

    无源汇上下界可行流

    又称作循环流,这个网络流是没有源点和汇点的,只有流量在上面不停地循环

    这个是其他上下界网络流的基础

    (n)个点,(m)条边,每条边(e)有一个流量下界(low)和流量上界(up),求一种可行方案使得在所有点满足流量平衡条件的前提下,所有边满足流量限制。

    两个限制一般会把自己搞掉,我们一个一个考虑,先考虑下界

    只要我们先给每条弧都加一个(low)的流量下界这个限制就没了对吧,但是现在这个图很可能不满足流量平衡,我们姑且称当前图上的流量为初始流

    我们应该在这张图上添加一个附加流,使得这个附加流加上初始流满足流量平衡

    显然我们还要考虑上界,每条弧的附加流上限是(up-low),所以我们建边的时候可以直接把容量设为(up-low),把(low)提出来

    考虑现在对于图中某个点(i),假设她现在的 总流入量-总流出量为(c_i)

    那么如果这个(c_i)大于(0),说明这个点她流入的流量有点多,应该流出一些(?

    如果这个(c_i)小于(0),说明她流出的太多了,应该注♂入♂灵♂魂♂

    那这些多出来的和应该补充进入的流量怎么办呢?

    我们可以设置虚拟源点和汇点

    虚拟源点向每个(c_i>0)的点连边,容量为(c_i)

    每个(c_i<0)的点向虚拟汇点连边,容量为(-c_i)

    有人会疑惑为什么流入更多的还要流入,流出更多的还要流出吗???

    因为我们并不是直接通过设置虚拟源点和汇点实现流量平衡的

    虚拟源点和汇点的作用是:把刚开始不平衡的局面构造出来,然后通过(dinic)算法和原有的边实现流量平衡

    如某个点(x),它的(c_x>0),那么我就从虚拟源点给他(c_x)的流,然后让这些流在原本的那些边上流动,直到流到某个(c_y<0)的点,通过这个(y)点流出

    然后留在原有边上的流量是我们要求的附加流

    如果我们能找到一个附加流加上初始流后流量守恒,那么直接与虚拟源点和虚拟汇点相连的边应该都满流

    由于(sumlimits_{iin V}[c_i>0]c_i=-sumlimits_{iin V}[c_i<0]c_i)

    所以如果能找到符合要求的附加流,那么在新图中的最大流应该等于(sumlimits_{iin V}[c_i>0]c_i)

    求出最大流后怎么找到每条边的流量呢?

    由于我们已经跑过(dinic)算法了,所以每条弧的反向弧中的流量就是这条弧所对应的附加流

    上板子

    展开查看
    
    ```cpp
    #include
    #include
    #include
    #include
    #include
    #include
    using namespace std;
    namespace red{
    #define eps (1e-8)
    	inline int read()
    	{
    		int x=0;char ch,f=1;
    		for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    		if(ch=='-') f=0,ch=getchar();
    		while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    		return f?x:-x;
    	}
    	const int N=1e5+10,inf=0x3f3f3f3f;
    	int n,m,sum;
    	int st,ed;
    	int down[N];
    	int head[N],cnt=1;
    	struct point
    	{
    		int nxt,to,val;
    		point(){}
    		point(const int &nxt,const int &to,const int &val):nxt(nxt),to(to),val(val){}
    	}a[N];
    	int c[N];
    	inline void link(int x,int y,int z)
    	{
    		a[++cnt]=(point){head[x],y,z};head[x]=cnt;
    		a[++cnt]=(point){head[y],x,0};head[y]=cnt;
    	}
    	int d[N],cur[N];
    	queue q;
    	inline bool bfs()
    	{
    		for(int i=1;i<=n+2;++i)
    		{
    			d[i]=0;
    			cur[i]=head[i];
    		}
    		q.push(st);d[st]=1;
    		while(!q.empty())
    		{
    			int now=q.front();
    			q.pop();
    			for(int i=head[now];i;i=a[i].nxt)
    			{
    				int t=a[i].to;
    				if(!d[t]&&a[i].val)
    				{
    					d[t]=d[now]+1;
    					q.push(t);
    				}
    			}
    		}
    		return d[ed];
    	}
    	inline int dfs(int now,int c)
    	{
    		if(now==ed||!c) return c;
    		int ret=c,f;
    		for(int i=cur[now];i;i=a[i].nxt)
    		{
    			cur[now]=i;
    			int t=a[i].to;
    			if(d[t]==d[now]+1)
    			{
    				f=dfs(t,min(a[i].val,ret));
    				a[i].val-=f;
    				a[i^1].val+=f;
    				ret-=f;
    				if(!ret) return c;
    			}
    		}
    		if(ret==c) d[now]=0;
    		return c-ret;
    	}
    	inline void dinic()
    	{
    		int ret=0;
    		while(bfs()) ret+=dfs(st,inf);
    		if(sum==ret)
    		{
    			puts("YES");
    			for(int i=3,j=1;j<=m;++j,i+=2)
    			{
    				printf("%d
    ",a[i].val+down[j]);
    			}
    		}
    		else puts("NO");
    	}
    	inline void main()
    	{
    		n=read(),m=read();
    		for(int x,y,up,i=1;i<=m;++i)
    		{
    			x=read(),y=read(),down[i]=read(),up=read();
    			link(x,y,up-down[i]);
    			c[y]+=down[i];
    			c[x]-=down[i];
    		}
    		st=n+1,ed=n+2;
    		for(int i=1;i<=n;++i)
    		{
    			if(c[i]>0) link(st,i,c[i]),sum+=c[i];
    			if(c[i]<0) link(i,ed,-c[i]);
    		}
    		dinic();
    	}
    }
    signed main()
    {
    	red::main();
    return 0;
    }
    ```
    

    有源汇上下界可行流

    (n)个点,(m)条边,每条边(e)有一个流量下界(low)和流量上界(up),给定源点(S)与汇点(T),求源点到汇点的可行流。

    这个东西就有点问题,因为(S)(T)流量不守恒,刚刚我们处理的情况是所有节点流量守恒的情况

    当然我们可以手动令它流量守恒,比如:由(T)(S)连一条下界为(0),上界为(inf)的边,就可以实现所有点流量守恒

    然后重复刚才的过程,求一个循环流

    然后把刚才加的那条边拆掉,我们就得到了一个有源汇上下界可行流

    考虑这个有源汇上下界可行流的流量是多少?由于所有的流都会流入汇点,所以我们只要看最后(T)(S)的反向弧中有多少流量

    有源汇上下界最大流

    (n)个点,(m)条边,每条边(e)有一个流量下界(low)和流量上界(up),给定源点(S)与汇点(T),求源点到汇点的最大流。

    利用刚才的方法,我们已经求得了一个有源汇上下界可行流,那我们怎么得到有源汇上下界最大流?

    由于目前我们的网络流中的流量已经平衡,所以我们只要在残量网络上再跑一个最大流即可

    最终的最大流=有源汇上下界可行流+残量网络上最大流

    考虑为什么不会超出上下界:

    下界:我们在建图的时候下界并没有出现在原图中,下界是额外计算的,一定大于下界

    上界:由于建边的时候流量为(up-low),所以不会超过上界

    其实实际操作起来很方便:只要先用虚拟源汇跑一遍(dinic),然后不删除(T)(S)的边,再用原本的源汇跑一遍(dinic),得到的结果就是答案

    为什么这样做是对的?

    因为考虑第一次用虚拟源汇跑完(dinic)后,原图中留下的本就是残量网络

    且原本的可行流应该保存在(T)(S)的反向边中,然而我们这次跑的是(S)(T)的最大流,这些流量刚好会被加上~

    出现了!是板子!

    展开查看
    
    ```cpp
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    using namespace std;
    namespace red{
    #define eps (1e-8)
    	inline int read()
    	{
    		int x=0;char ch,f=1;
    		for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    		if(ch=='-') f=0,ch=getchar();
    		while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    		return f?x:-x;
    	}
    	const int N=1e5+10,inf=0x3f3f3f3f;
    	int n,m,st,ed,stt,edd,ans,sum;
    	int c[N],x[N],y[N],low[N],up[N],e[N];
    	int head[N],cur[N],cnt=1;
    	struct point
    	{
    		int nxt,to,val;
    		point(){}
    		point(const int &nxt,const int &to,const int &val):nxt(nxt),to(to),val(val){}
    	}a[N];
    	inline void link(int x,int y,int z)
    	{
    		a[++cnt]=(point){head[x],y,z};head[x]=cnt;
    		a[++cnt]=(point){head[y],x,0};head[y]=cnt;
    	}
    	int d[N];
    	queue q;
    	inline bool bfs()
    	{
    		for(int i=1;i<=n+2;++i)
    		{
    			cur[i]=head[i];
    			d[i]=0;
    		}
    		q.push(stt);d[stt]=1;
    		while(!q.empty())
    		{
    			int now=q.front();
    			q.pop();
    			for(int i=head[now];i;i=a[i].nxt)
    			{
    				int t=a[i].to;
    				if(!d[t]&&a[i].val)
    				{
    					d[t]=d[now]+1;
    					q.push(t);
    				}
    			}
    		}
    		return d[edd];
    	}
    	inline int dfs(int now,int c)
    	{
    		if(now==edd||!c) return c;
    		int ret=c,f;
    		for(int i=cur[now];i;i=a[i].nxt)
    		{
    			cur[now]=i;
    			int t=a[i].to;
    			if(d[t]==d[now]+1)
    			{
    				f=dfs(t,min(ret,a[i].val));
    				a[i].val-=f;
    				a[i^1].val+=f;
    				ret-=f;
    				if(!ret) return c;
    			}
    		}
    		if(ret==c) d[now]=0;
    		return c-ret;
    	}
    	inline void dinic()
    	{
    		int ret=0;
    		while(bfs()) ret+=dfs(stt,inf);
    		if(sum!=ret)
    		{
    			puts("please go home to sleep");
    			return;
    		}
    		stt=st,edd=ed;
    		while(bfs()) ans+=dfs(stt,inf);
    		printf("%d
    ",ans);
    	}
    	inline void main()
    	{
    		n=read(),m=read(),st=read(),ed=read();
    		for(int i=1;i<=m;++i)
    		{
    			x[i]=read(),y[i]=read(),low[i]=read(),up[i]=read();
    			link(x[i],y[i],up[i]-low[i]);
    			c[y[i]]+=low[i];
    			c[x[i]]-=low[i];
    		}
    		stt=n+1,edd=n+2;
    		for(int i=1;i<=n;++i)
    		{
    			if(c[i]>0) link(stt,i,c[i]),sum+=c[i];
    			if(c[i]<0) link(i,edd,-c[i]);
    		}
    		link(ed,st,inf);
    		dinic();
    	}
    }
    signed main()
    {
    	red::main();
    return 0;
    }
    ```
    

    有源汇上下界最小流

    (n)个点,(m)条边,每条边(e)有一个流量下界(low)和流量上界(up),给定源点(S)与汇点(T),求源点到汇点的最小流。

    思路完全类似,先求出有源汇上下界可行流

    考虑在可行流基础上减去一个守恒的流量

    需要深入理解(dinic)的反向边,我们如果找到一条从(T)(S)的增广路,就说明去掉这条增广路上的流量,网络流仍然守恒

    所以我们可以直接从(T)(S)跑一波最大流,用可行流减去这次最大流的值,即为有源汇上下界最小流

    注意这次不能留下那条(T)(S)(inf)边了(qwq)

    上下界分析相同

    看!又是板子!

    展开查看
    
    ```cpp
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    using namespace std;
    namespace red{
    #define int long long
    #define eps (1e-8)
    	inline int read()
    	{
    		int x=0;char ch,f=1;
    		for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    		if(ch=='-') f=0,ch=getchar();
    		while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    		return f?x:-x;
    	}
    	const int N=5e5+10,inf=0x3f3f3f3f3f3f3f3f;
    	int n,m,st,ed,stt,edd,ans,sum;
    	int c[N],x[N],y[N],low[N],up[N],e[N];
    	int head[N],cur[N],cnt=1;
    	struct point
    	{
    		int nxt,to,val;
    		point(){}
    		point(const int &nxt,const int &to,const int &val):nxt(nxt),to(to),val(val){}
    	}a[N];
    	inline void link(int x,int y,int z)
    	{
    		a[++cnt]=(point){head[x],y,z};head[x]=cnt;
    		a[++cnt]=(point){head[y],x,0};head[y]=cnt;
    	}
    	int d[N];
    	queue q;
    	inline bool bfs()
    	{
    		for(int i=1;i<=n+2;++i)
    		{
    			cur[i]=head[i];
    			d[i]=0;
    		}
    		q.push(stt);d[stt]=1;
    		while(!q.empty())
    		{
    			int now=q.front();
    			q.pop();
    			for(int i=head[now];i;i=a[i].nxt)
    			{
    				int t=a[i].to;
    				if(!d[t]&&a[i].val)
    				{
    					d[t]=d[now]+1;
    					q.push(t);
    				}
    			}
    		}
    		return d[edd];
    	}
    	inline int dfs(int now,int c)
    	{
    		if(now==edd||!c) return c;
    		int ret=c,f;
    		for(int i=cur[now];i;i=a[i].nxt)
    		{
    			cur[now]=i;
    			int t=a[i].to;
    			if(d[t]==d[now]+1)
    			{
    				f=dfs(t,min(ret,a[i].val));
    				a[i].val-=f;
    				a[i^1].val+=f;
    				ret-=f;
    				if(!ret) return c;
    			}
    		}
    		if(ret==c) d[now]=0;
    		return c-ret;
    	}
    	inline void dinic()
    	{
    		int ret=0;
    		while(bfs()) ret+=dfs(stt,inf);
    		if(sum!=ret)
    		{
    			puts("please go home to sleep");
    			return;
    		}
    		ret=a[cnt].val;
    		stt=ed,edd=st;
    		for(int i=head[ed];i;i=a[i].nxt)
    		{
    			int t=a[i].to;
    			if(t==st) a[i].val=a[i^1].val=0;
    		}
    		while(bfs()) ans+=dfs(stt,inf);
    		printf("%lld
    ",ret-ans);
    	}
    	inline void main()
    	{
    		n=read(),m=read(),st=read(),ed=read();
    		for(int i=1;i<=m;++i)
    		{
    			x[i]=read(),y[i]=read(),low[i]=read(),up[i]=read();
    			link(x[i],y[i],up[i]-low[i]);
    			c[y[i]]+=low[i];
    			c[x[i]]-=low[i];
    		}
    		stt=n+1,edd=n+2;
    		for(int i=1;i<=n;++i)
    		{
    			if(c[i]>0) link(stt,i,c[i]),sum+=c[i];
    			if(c[i]<0) link(i,edd,-c[i]);
    		}
    		link(ed,st,inf);
    		dinic();
    	}
    }
    signed main()
    {
    	red::main();
    return 0;
    }
    ```
    
  • 相关阅读:
    linux三剑客之grep
    MySQL练习(1)
    appium获取toast方法
    Could not parse UiSelector argument: 'XXX' is not a string 错误解决办法
    基于python的几种排序算法的实现
    生成allure测试报告之后,服务器端口无法访问查看生成的report,可能是这样引起的。
    通过源码看原理之 selenium
    如何查看浏览器记住的密码
    传智播客JavaWeb day09-mysql入门、数据库操作、数据库表操作、数据行操作
    SQLServer数据库表架构和数据保存成sql文件
  • 原文地址:https://www.cnblogs.com/knife-rose/p/12098751.html
Copyright © 2020-2023  润新知