网络流最大流----Dinic算法

关于网络流的一些基本概念我已经在之前介绍EK算法的时候介绍过了,不懂的小伙伴可以看下这里:https://blog.csdn.net/AC__dream/article/details/126457160

先来回顾一下EK算法,我们在每次dfs求解增广路的时候都是只找到一条增广路,然后把这条增广路所带来的贡献计入答案,一直按照这个方法循环,最后当bfs无法找到一条新的增广路的时候算法就结束了,总的复杂度是O(n*m*m)其中n是点数,m是边数,一般的图m是要远大于n的,所以EK算法能解决的问题的数据规模还是比较小的,而Dinic算法的复杂度是O(n*n*m),相比于EK算法来说复杂度就会小很多,那Dinic算法相对于EK算法到底优在哪呢?其实就是在找增广路径的时候,EK算法是一次bfs只能找到一条,而Dinic算法是一次dfs可以计算多条增广路径,这样会极大地优化求解最大流的复杂度。

为了实现一次dfs能够计算多条增广路的贡献,Dinic算法首先对点进行了分层(因为Dinic的dfs过程是根据层次进行搜索的),这是通过bfs实现的:(以下图为例)

首先将所有点的层次设为0,然后将起点1的层次标记为1,1一次能够到达的点有2,3,所以d[2]=d[3]=2,然后第二层一次能够到达的点记为第三层,即d[4]=d[5]=d[6]=d[7]=3,最后就是d[8]=4,注意:虽然2能一步到达3,但是由于3的层次已经被标记为2,所以不需要再次对3进行标记,这样就对图分完层了。注意分层的前提是边的权值不能为0,如果边权为0相当于这条边已经没办法继续分流,所以也就没必要考虑权值为0的边。如果我们在bfs过程中没办法走到汇点8,那么就相当于我们已经无法找到增广路径,那么我们的算法也就结束了,这一点是类似于EK算法的。

dfs的过程中我们要记录两个量,一个是x,代表当前所在的点,另一个是mf,代表当前所在的点前面经过的路径中的剩余流量,是用于后续流量分配的。每次从x搜索完一条边后都需要进行流量分配,如果剩余流量剩余为0那么我们就没必要继续搜索了,在回溯的过程中对边权进行修改,并统计流量,注意:在我们搜索过程中只能从深度为x的点向深度为x+1的点的方向进行搜索,这样会优化搜索路径。

下面说一个技巧,就是我们搜索以x为起点的边时,有可能之前已经搜索过一部分边,那么已经搜索过的边原则上是不可能找到新的增广路的,所以我们没有必要再次进行搜索,所以我们应该用一个数组记录上次搜索到的边的编号,然后下次直接从该边进行搜索即可,这个数组就是cur

Dinic的思路大致就讲完了

下面给出上面这个图的模拟:(图片来自bilibili董晓算法)

最后分享一道题,也是洛谷的那道模板题:

题目链接:【模板】网络最大流 - 洛谷

在之前已经用EK算法给出过代码,接下来用Dinic算法给出代码,里面已经给出注释,大家可以结合注释进行理解:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=203,M=2e5+10;
int h[N],e[M],ne[M],idx;
long long w[M];
int cur[N];//cur[]用于弧优化
int d[N];//d[i]记录第i个节点的深度
int n,m,s,t;
void add(int x,int y,int z)
{
	e[idx]=y;
	w[idx]=z;
	ne[idx]=h[x];
	h[x]=idx++;
}
bool bfs()//对点分层,找增广路 
{
	memset(d,0,sizeof d);
	d[s]=1;//将源点深度设置为1
	queue<int>q;
	q.push(s);
	while(!q.empty())
	{
		int x=q.front();
		q.pop();
		for(int i=h[x];i!=-1;i=ne[i])
		{
			int j=e[i];
			if(!d[j]&&w[i])//只加入边权不为0的边,边权为0的边相当于已经被删除,不在分层图中作保留 
			{
				d[j]=d[x]+1;
				q.push(j);
				if(j==t) return true;
			}
		}
	}
	return false;
}
long long dfs(int x,long long mf)//多路增广,mf是余量
{
	if(x==t) return mf;
	long long ans=0;
	for(int i=cur[x];i!=-1;i=ne[i])//注意这里进行了弧优化,不是直接用h数组进行遍历,因为前面遍历过的已经不可能找到可行流 
	{
		int j=e[i];
		cur[x]=i;//更新弧优化,使得下一次搜索的边都是前面未搜索过的边 
		if(d[j]==d[x]+1&&w[i])//按照点的所在层次进行深搜 
		{
			long long t=dfs(j,min(mf,w[i]));//记录扩增流大小 
			w[i]-=t;
			w[i^1]+=t;
			ans+=t;
			mf-=t;//减少x的剩余流量,因为流量已经被当前边占用了t 
			if(mf==0) break;//余量优化,当没有余量的时候就没必要继续搜索了 
		}
	}
	if(ans==0) d[x]=0;//残支优化,这条路是不可能找到可行流的 
	return ans;
}
long long Dinic()
{
	long long ans=0;
	while(bfs())
	{
		memcpy(cur,h,sizeof h);//将初边赋给cur 
		ans+=dfs(s,1e13);
	}
	return ans;
}
int main()
{
	cin>>n>>m>>s>>t;
	for(int i=1;i<=n;i++) h[i]=-1;
	for(int i=1;i<=m;i++)
	{
		int u,v,z;
		scanf("%d%d%d",&u,&v,&z);
		add(u,v,z);add(v,u,0);
	}
	printf("%lld\n",Dinic());
	return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值