网络流学习笔记

网络流

  • 目的
  • 思路
  • 实现
    • Code
    • 优化
      • Code

目的

传说中的省选算法。首先我们得明确网络流是用来干什么的,可以看一下下面这道例题:usaco草地排水。这一题就是网络流的模板题。

给定指定的一个有向图,其中有两个特殊的点源S(Sources)和汇T(Sinks),每条边有指定的容量(Capacity),求满足条件的从S到T的最大流(MaxFlow)。

这题的题意稍微解释一下,我们就知道网络流是干什么的了。草地排水这一题题目大意就是从水潭经过水沟能流到小溪的最大流量。

思路

首先先给出一幅图

1
1
1
1
1
1
2
3
4

先引入一个概念 残量图
从名字上来看,残,就是剩下的意思。残量就是最大流量 M A X − MAX- MAX实际流量 F F F,即残量   = M A X − F ~=MAX-F  =MAXF
如果某一次从源出发能到达汇,说明这条路上还有流量能到达汇,这样的路径叫做增广路。
上图中我们从源出发到达汇,可以得到增广路 ( 1 , 2 , 3 , 4 ) (1,2,3,4) (1,2,3,4),得到残量图:

0
1
1
0
0
1
2
3
4

这时候我们从源已经不能到达汇了,这时候的最大流也就是1了。但是答案并不对,我们可以找到 ( 1 , 2 , 4 ) , ( 1 , 3 , 4 ) (1,2,4),(1,3,4) (1,2,4)(1,3,4)两条增广路,也就是说我们的程序按错误的路走到了汇,但我们并没有给程序一个后悔的机会,所以这时候我们加入一个反向边的概念,每一次当前边的权值 + a 或 − a +a或-a +aa,对应了其反向边的 − a 或 + a -a或+a a+a
我们将上面的残量图加入反向边,又会得到一条增广路 ( 1 , 3 , 2 , 4 ) (1,3,2,4) (1,3,2,4)。这时候的最大流就是2了。这就是正确答案了。为什么引入反向边后答案就正确了呢?妙就妙在第二个 d f s dfs dfs,当我们建立反向边后,我们相当于把 ( 2 , 3 ) (2,3) (2,3)流量退回去了,于是我们在 d f s dfs dfs就正确了。

实现

大致思路讲完了,接下来讲讲怎么实现吧。
首先我们可以先用 b f s bfs bfs分层,如果分层到不了汇,表示当前已经断层了,流量到不了终点了,结束程序。
如果没有断层,就考虑从源开始 d f s dfs dfs,每次找增广路。
这里就已经体现出了 d i n i c dinic dinic算法的思路,不断地 b f s , d f s , b f s … … bfs,dfs,bfs…… bfs,dfs,bfs直到断层找不到增广路。
具体细节还是看看代码理解一下吧。

Code

#include
#include
#include
#include
using namespace std;
int m,n,head[1000005],dep[1000005],next[1000005],road[1000005];
int dis[1000005],cnt=-1;
int q[1000005];
void add(int u,int v,int ds)
{
     
	next[++cnt]=head[u];
	head[u]=cnt;
	road[cnt]=v;
	dis[cnt]=ds;
}
int bfs()
{
     
	memset(dep,-1,sizeof(dep));//记得不要忘了这条语句
	int t=0,w=1;
	q[w]=1;dep[1]=0;
	while(t<w)
	{
     
		t++;
		for(int i=head[q[t]];i!=-1;i=next[i])
		{
     
			int v=road[i];
			if(dep[v]==-1&&dis[i])//如果还没有深度,并且边不为0
			{
     
				dep[v]=dep[q[t]]+1;
				w++;
				q[w]=v;
			}
		}
	}
	return dep[n]!=-1;
}
int dfs(int u,int flo)
{
     
	if(u==n) return flo;//如果已经到了汇直接返回当前残量
	int delta=flo;//用delta记录残量方便更改
	for(int i=head[u];i!=-1;i=next[i])
	{
     
		int v=road[i];
		if(dep[v]==dep[u]+1&&dis[i]>0)//如果符合深度并且边权不为0
		{
     
			int d=dfs(v,min(delta,dis[i]));
			//dfs残量为当前残量和边权的较小值
			dis[i]-=d,dis[i^1]+=d;
			//注意这里的i^1表示边i的反向边这里是由我们存图时的cnt决定的
			delta-=d;//将残量-当前流量
			if(!delta) break;//如果残量为0可以退出
		}
	}
	return flo-delta;//返回已经被流过的合法流量
}
int dinic()//dinic算法核心
{
     
	int ans=0;
	while(bfs())//bfs分层
		ans+=dfs(1,999999999);
	//由于这个时候我们不知道残量是多少,用+INF表示
	return ans;
}
int main()
{
     
	memset(head,-1,sizeof(head));
	memset(next,-1,sizeof(next));
	scanf("%d%d",&m,&n);
	for(int i=1;i<=m;i++)
	{
     
		int u,v,ds;
		scanf("%d%d%d",&u,&v,&ds);
		add(u,v,ds),add(v,u,0);//链式前向星存图
	}
	printf("%d",dinic());
}

优化

不知道那个脑子灵光的人想出的优化
如果节点 1 , 2 , 3 , 4 1,2,3,4 1,2,3,4的流量都已为0,那么我们下次就可以从节点 5 5 5开始搜索了,可以用一个 c u r cur cur数组记录,每次将 h e a d head head数组拷到 c u r cur cur数组, d f s dfs dfs时顺带记录即可。

Code

#include
#include
#include
#include
using namespace std;
int m,n,head[1000005],dep[1000005],next[1000005],road[1000005];
int dis[1000005],cnt=-1;
int q[1000005],cur[1000005];
void add(int u,int v,int ds)
{
     
	next[++cnt]=head[u];
	head[u]=cnt;
	road[cnt]=v;
	dis[cnt]=ds;
}
int bfs()
{
     
	memset(dep,-1,sizeof(dep));
	int t=0,w=1;
	q[w]=1;dep[1]=0;
	while(t<w)
	{
     
		t++;
		for(int i=head[q[t]];i!=-1;i=next[i])
		{
     
			int v=road[i];
			if(dep[v]==-1&&dis[i])
			{
     
				dep[v]=dep[q[t]]+1;
				w++;
				q[w]=v;
			}
		}
	}
	return dep[n]!=-1;
}
int dfs(int u,int flo)
{
     
	if(u==n) return flo;
	int delta=flo;
	for(int i=cur[u];i!=-1;i=next[i])
	{
     
		cur[u]=next[i];//记录cur数组
		int v=road[i];
		if(dep[v]==dep[u]+1&&dis[i]>0)
		{
     
			int d=dfs(v,min(delta,dis[i]));
			dis[i]-=d,dis[i^1]+=d;
			delta-=d;
			if(!delta) break;
		}
	}
	return flo-delta;
}
int dinic()
{
     
	int ans=0;
	while(bfs())
	{
     
		for(int i=1;i<=n;i++)
			cur[i]=head[i];//cur数组优化
		ans+=dfs(1,999999999);
	}
	return ans;
}
int main()
{
     
	memset(head,-1,sizeof(head));
	memset(next,-1,sizeof(next));
	scanf("%d%d",&m,&n);
	for(int i=1;i<=m;i++)
	{
     
		int u,v,ds;
		scanf("%d%d%d",&u,&v,&ds);
		add(u,v,ds),add(v,u,0);
	}
	printf("%d",dinic());
}

你可能感兴趣的:(网络流)