【学习笔记】网络流

前言:
网络流就像自来水厂到你家的水管,自来水厂(源点S)源源不断的提供水,水通过不同水管汇集于你家(设自来水厂的水全到你家,汇点T)。自来水厂到你家的水管网是一个复杂的有向图,每一节水管都有一个最大承载流量(容量)。自来水厂不放水,你家就没水了。但是就算自来水厂拼命地往管网里面注水,你家收到的水流量也是上限,毕竟每根水管承载量有限。这就是一个有向图,所有的水都从一个点流出(水厂),最后全部汇聚到一个点(你家)。

网络

网络(流网络)是指一个有向联通图,由一个点集和一个边集构成 G=(V,E)

  • 图中每个边都有一个属性,称为容量 c(u,v),表示最大能够通过的水量(或其他限制条件)。
  • 图中有两个特殊点:源点(S)和汇点(T)。源点 S 有无限多的水流可以向外流出,汇点 T 可以接受无限多的水流。

ps:不考虑反向边,如果有反向边,通过加点变成没有反向边的图。
如果一条边不存在,则定义这条边的容量是0。

image



对于一个网络流图 G=(V,E),每条边(u,v)上给定一个实数 f(u,v),f(u,v) 为 边(u,v) 上的流量。
对于任何一条有向边(u,v),f(u,v)=-f(v,u)

一个可行流f需满足两个条件:

  1. 流量守恒:x(VS,T)(v,x)Ef(v,x)=(x,v)Ef(x,v)
    除源点和汇点外,流入其余每一个点的流量和等于流出这个点的流量和,不存储流量。
    image

  2. 容量限制 0 f(u,v) c(u,v)
    我们永远赚不到自己认知以外的钱,水管也永远装不下比自己容量还大的水

|f| 表示可行流的流量值,定义为从源点流出的流量或汇点流入的流量.
|f| =(s,v)ϵEf(s,v) − (v,s)ϵEf(v,s)

和流网络的关系:一个流网络中有很多个可行流,G{f1f2f3



残留网络

流网络里的每一个可行流f都有自己的残留网络Gf,对于不同的可行流,残留网络不同。

G{f1Gf1f2Gf2f3Gf3

残留网络同样也是由一个点集和一个边集构成,Gf=(Vf,Ef)

  • 点集包含原网络所有点(Vf=V),
  • 边集包括原网络中所有边和所有反向边(Ef=E E中所有反向边)。

而残留网络中,边的容量c'(u,v)是原网络的残留容量

c'(u,v)={c(u,v)f(u,v)(u,v)E,f(v,u)(v,u)E,退

image
流量计算:|f+f'|=|f|+|f'|
流量相加指的是每条边对应相加:残留网络和原网络边的方向相同,累加;相反,相当于退回的流量,减去;

关于反向边

反向边是一个很牛逼的反悔机制,可以把前面流的流量退回去。这样,就能多去考虑被之前的通路阻断的情况,不会漏解。换句话说,反向边的存在让我们可以在所有的情况里选取最符合我们所需的情况。

原网络的可行流(f)和残留网络可行流(f’)有啥关系?

f'+f也是原网络G的另外一个可行流。

证明:

1.流量守恒证明

f和f'都满足流量守恒

x(V{S,T})(v,x)Ef(v,x)+(v,x)Ef(v,x)=(x,v)Ef(x,v)+(x,v)Ef(x,v)

f+f'满足流量守恒

2.容量限制证明

  • 考虑正向边,当c'(u,v)=c(u,v)-f(u,v) 时

0 f'(u,v) c'(u,v)=c(u,v)-f(u,v)

0 f'(u,v) c'(u,v)=c(u,v)-f(u,v)

0 f'(u,v) + f(u,v) c(u,v) 满足正向边容量限制

  • 考虑反向边,当c'(u,v)=f(v,u)时

0 f'(u,v) c'(u,v)=f(v,u) c(v,u)

0 f(v,u) - f'(u,v) c(u,v)

0 f(v,u) + f'(v,u) c(u,v) 满足反向边容量限制



增广路

残留网络里,从源点沿着流量 > 0的边能够走到汇点,这样的路径为增广路径。
对于当前流网络里的可行流来说,残留网络里无增广路径,则该可行流为最大流。



最大流

对于一个流网络来说,有很多可行流最大流是最大可行流。

image



EK算法

基于FF方法的一种算法

由最大流的定义得知,当残留网络里,不存在增广路时,当前可行流为最大流,那么EK算法就是把增广路一条一条的找到,那么不断地消除这一条条增广路,从而求得最大流。对增广路对应的原图里面的路径增流,就可以消除这条增广路。

  • 找增广路
    在残量网络中,任意找一条从 S 到 T 的路径,边权均不为 0,则这条路径是一条增广路。这里我用的是bfs
  • 更新残留网络
    在找到一条增广路后,找出路径上的边权最小值 x ,然后把路径上所有边权都减去x(也就是找增广路后,这条路径上流过 x 的流量)

我们不断的重复上面两个操作,直到找不到增广路为止,则没有一条路径可以给汇点增加流量。此时,汇点汇集的流量为最大流。
那么如果一条更优的路径被前面消除的一条路截断了怎么办呢?
这个时候,反向边的反悔机制,就起到了很牛逼的作用,我直接反悔,把这个之前通过增流消除的增广路进行退流,把之前增加的流量退回来,对当前更优的增广路进行消除,就ok了

代码实现:

点击查看代码
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N=1010,M=20010,inf=1e8;
int e[M],ne[M],f[M];
int q[N],d[N],h[N],pre[N],idx;
int m,n,S,T;
bool st[N];
void add(int a,int b,int c)
{
	e[idx]=b,f[idx]=c,ne[idx]=h[a],h[a]=idx++;
	e[idx]=a,f[idx]=0,ne[idx]=h[b],h[b]=idx++;
}
bool bfs()
{
	int tt=0,hh=0;
	memset(st,0,sizeof st);
	q[0]=S,st[S]=true,d[S]=inf;
	while(hh<=tt)
	{
		int t=q[hh++];
		for(int i=h[t];~i;i=ne[i])
		{
			int ver=e[i];
			if(!st[ver]&&f[i])
			{
				st[ver]=true;
				d[ver]=min(f[i],d[t]);
				pre[ver]=i;
				if(ver==T) return true;
				q[++tt]=ver;
			}
		}
	}
	return false;
}
inline int EK()
{
	int ans=0;
	while(bfs())
	{
		ans+=d[T];
		for(int i=T;i!=S;i=e[pre[i]^1])
		{
			f[pre[i]]-=d[T],f[pre[i]^1]+=d[T];
		}
	}
	return ans;
}
int main()
{
	scanf("%d%d%d%d",&n,&m,&S,&T);
	memset(h,-1,sizeof h);
	for(int i=1;i<=m;i++)
	{
		int a,b,c;
		cin>>a>>b>>c;
		add(a,b,c);
	}
	printf("%d\n",EK());
	return 0;
}


Dinic算法

blablablabla...
对EK算法的一种优化

在EK的基础上建立分层图,
每次一层层跑最短路 相比于EK算法 减少了很多不必要的循环 可以更高效的消除增广路

点击查看代码
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N=10100,M=200100,inf=1e8;
int e[M],ne[M],f[M];
int q[N],d[N],h[N],cur[N],idx;
int m,n,S,T;

void add(int a,int b,int c)
{
	e[idx]=b,f[idx]=c,ne[idx]=h[a],h[a]=idx++;
	e[idx]=a,f[idx]=0,ne[idx]=h[b],h[b]=idx++;
}
bool bfs()
{
	int tt=0,hh=0;
	memset(d,-1,sizeof d);
	q[0]=S,d[S]=0,cur[S]=h[S];
	while(hh<=tt)
	{
		int t=q[hh++];
		for(int i=h[t];~i;i=ne[i])
		{
			int ver=e[i];
			if(d[ver]==-1&&f[i])
			{
				d[ver]=d[t]+1;
				cur[ver]=h[ver];
				if(ver==T) return true;
				q[++tt]=ver;
			}
		}
	}
	return false;
}
inline int find(int u,int limits)
{
	if(u==T) return limits;
	int flow=0;
	for(int i=cur[u];~i&&flow<limits;i=ne[i])
	{
		int ver=e[i];
		cur[u]=i;
		if(d[ver]==d[u]+1&&f[i])
		{
			int t=find(ver,min(f[i],limits-flow));
			if(!t) d[ver]=-1;
			f[i]-=t;
			f[i^1]+=t;
			flow+=t;
		}
	}
	return flow;
}
inline int Dinic()
{
	int ans=0,flow;
	while(bfs())
	{
		while(flow=find(S,inf)) ans+=flow;
	}
	return ans;
}
int main()
{
	scanf("%d%d%d%d",&n,&m,&S,&T);
	memset(h,-1,sizeof h);
	for(int i=1;i<=m;i++)
	{
		int a,b,c;
		cin>>a>>b>>c;
		add(a,b,c);
	}
	printf("%d\n",Dinic());
	return 0;
}


无源汇上下界可行流

点击查看代码
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N=1010,M=30010,inf=1e8;
int e[M],ne[M],f[M],l[M];
int q[N],d[N],h[N],cur[N],idx,A[N];
int m,n,S,T;

void add(int a,int b,int c,int d)
{
	e[idx]=b,f[idx]=d-c,l[idx]=c,ne[idx]=h[a],h[a]=idx++;
	e[idx]=a,f[idx]=0,ne[idx]=h[b],h[b]=idx++;
}
bool bfs()
{
	int tt=0,hh=0;
	memset(d,-1,sizeof d);
	q[0]=S,d[S]=0,cur[S]=h[S];
	while(hh<=tt)
	{
		int t=q[hh++];
		for(int i=h[t];~i;i=ne[i])
		{
			int ver=e[i];
			if(d[ver]==-1&&f[i])
			{
				d[ver]=d[t]+1;
				cur[ver]=h[ver];
				if(ver==T) return true;
				q[++tt]=ver;
			}
		}
	}
	return false;
}
inline int find(int u,int limit)
{
	if(u==T) return limit;
	int flow=0;
	for(int i=cur[u];~i&&flow<limit;i=ne[i])
	{
		int ver=e[i];
		cur[u]=i;
		if(d[ver]==d[u]+1&&f[i])
		{
			int t=find(ver,min(f[i],limit-flow));
			if(!t) d[ver]=-1;
			f[i]-=t;
			f[i^1]+=t;
			flow+=t;
		}
	}
	return flow;
}
inline int Dinic()
{
	int ans=0,flow;
	while(bfs())
	{
		while(flow=find(S,inf)) ans+=flow;
	}
	return ans;
}
int main()
{
	scanf("%d%d",&n,&m);
	S=0,T=n+1;
	memset(h,-1,sizeof h);
	for(int i=1;i<=m;i++)
	{
		int a,b,c,d;
		cin>>a>>b>>c>>d;
		add(a,b,c,d);
		A[a]-=c,A[b]+=c;
	}
	int tot=0;
	for(int i=1;i<=n;i++)
	{
		if(A[i]>0)
		{
			add(S,i,0,A[i]);
			tot+=A[i];
		}
		else if(A[i]<0)
		{
			add(i,T,0,-A[i]);
		}
	}
	if(Dinic()!=tot) puts("NO");
	else 
	{
		puts("YES");
		for(int i=0;i<2*m;i+=2)
		{
			printf("%d\n",f[i^1]+l[i]);
		}
	}
	return 0;
}


有时间再写..

posted @   watasky  阅读(256)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端
点击右上角即可分享
微信分享提示