[模板] 网络最大流 Dinic

网络流

一些定义和性质

  • 建立反边,从 \((u,v)\) 流了 \(res\) 的流量就相当于给 \((v,u)\) 增加了 \(res\) 的流量

  • 由于一个割肯定是 \(\geq\) 割的净流量的,所以最大流等于最小割

基本思路就是:在残量网络不断找增广路直到找不到为止

FF 算法

所以就诞生了下面的暴力 \(FF\) 算法代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define LL long long
const int maxn = 205,maxm = 5000 + 10;
struct edge{
	int to,nxt;
	LL w;
}e[maxm<<1];
int n,m,s,t;
int cnt=1,head[maxn];
bool vis[maxm];
inline void link(int u,int v,LL w=0){
	e[++cnt].to=v;e[cnt].w=w;e[cnt].nxt=head[u];head[u]=cnt;
}
LL dfs(int u,LL flow){
	if(u==t)return flow;
	vis[u]=true;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(e[i].w==0||vis[v])continue;
		LL res=0;
		if((res=dfs(v,min(flow,e[i].w)))>0){
			e[i].w-=res;
			e[i^1].w+=res;
			return res;
		}
	}
	return 0;
}
int main(){
	scanf("%d%d%d%d",&n,&m,&s,&t);
	for(int i=1,u,v;i<=m;i++){
		LL w;
		scanf("%d%d%lld",&u,&v,&w);
		link(u,v,w);link(v,u);
	}
	LL res=0,tot=0;
	while(memset(vis,false,sizeof vis) && (res=dfs(s,1e18))>0)tot+=res;
	printf("%lld\n",tot);
	return 0;
}
  • 还有一种方法还可以多条路放水增广
long long dfs(int x,long long v){
	if(x==t||v==0)return v;
	long long res=0;
	vis[x]=1;
	for(int i=f[x];i;i=ne[i]){
		if(vis[to[i]]||is[to[i]])continue;
		long long u=dfs(to[i],min(v,w[i]));
		v-=u;
		res+=u;
		w[i]-=u;
		w[i^1]+=u;
		if(!v)break;
	}
	if(!res)is[x]=1;
	return res;
}

\(Dinic\) 算法

由于每次只找一条路,这条路还可能绕远路(可能经过 n 个点才到达汇点),而且增加流量是路上最小的权值,效率低。网上有些题解试图简单卡 FF。

这个优化体现在:

  • 每次多路增广:如果当前结点还有没用完的供给,就考虑其他边

怎么避免绕路的情况:

每次 \(bfs\) 对残量网络进行分层,只有进入下一层时才可增广就可以避免混乱

  • 每回合从源点出发,先按照当前残量网络进行分层,然后多路增广,尽可能增加流量
  • 只要 \(bfs\) 可以搜到 \(t\) ,就可以继续增广
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
#define LL long long
const int maxn = 10000 + 100,maxm = 200000 + 100;
int n,m,s,t;
LL val[maxn];
int head[maxn],de[maxn],cnt=1;
struct edge{
	int to,nxt;
}e[maxm];
inline void link(int u,int v,LL w){
	e[++cnt].to=v;e[cnt].nxt=head[u];val[cnt]=w;head[u]=cnt;
}
bool bfs(){
	queue<int> q;
	memset(de,0,sizeof de);
	q.push(s);de[s]=1;
	while(q.size()){
		int u=q.front();q.pop();
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].to;
			if(val[i] && !de[v]){
				de[v]=de[u]+1;
				q.push(v);
			}
		} 
	}
	return de[t];
}
LL dfs(int u,LL flow){
	if(u==t)return flow;
	LL out=0;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		LL res=0;
		if(val[i] && de[v]==de[u]+1){
			res+=dfs(v,min(flow,val[i]));
			val[i]-=res;val[i^1]+=res;
			flow-=res;
			out+=res;
            if(!flow)break;
		}
	}
	if(out==0)de[u]=0;
	return out;
}
int main(){
	scanf("%d%d%d%d",&n,&m,&s,&t);
	while(m--){
		int u,v;LL w;
		scanf("%d%d%lld",&u,&v,&w);
		link(u,v,w);link(v,u,0);
	}
	LL ans=0;
	while(bfs())ans+=dfs(s,1e18);
	printf("%lld\n",ans);
	return 0;
}

当前弧优化

  • 基本思想

对于一个节点 \(x\),当它在 \(DFS\) 中走到了第 \(i\) 条弧时,前 \(i−1\) 条弧到汇点的流一定已经被流满而没有可行的路线了

所以我们可以在每次枚举节点 \(x\) 所连的弧时,改变枚举的起点,这样就可以删除起点以前的所有弧,来达到优化剪枝的效果

  • 具体做法

用一个数组 \(now\) 先把 \(head\) 复制一份,然后一边枚举出弧一边记录就行了

最后的成品代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
#define LL long long
const int maxn = 10000 + 100,maxm = 200000 + 100;
int n,m,s,t;
LL val[maxn];
int head[maxn],now[maxn],de[maxn],cnt=1;
struct edge{
	int to,nxt;
}e[maxm];
inline void link(int u,int v,LL w){
	e[++cnt].to=v;e[cnt].nxt=head[u];val[cnt]=w;head[u]=cnt;
}
bool bfs(){
	queue<int> q;
	memset(de,0,sizeof de);
	q.push(s);de[s]=1;
	now[s]=head[s];
	while(q.size()){
		int u=q.front();q.pop();
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].to;
			if(val[i] && !de[v]){
				now[v]=head[v];
				de[v]=de[u]+1;
				q.push(v);
			}
		} 
	}
	return de[t];
}
LL dfs(int u,LL flow){
	if(u==t)return flow;
	LL out=0;
	for(int i=now[u];i;i=e[i].nxt){
		now[u]=i;
		int v=e[i].to;
		LL res=0;
		if(val[i] && de[v]==de[u]+1){
			res+=dfs(v,min(flow,val[i]));
			val[i]-=res;val[i^1]+=res;
			flow-=res;
			out+=res;
            if(!flow)break;
		}
	}
	if(out==0)de[u]=0;
	return out;
}
int main(){
	scanf("%d%d%d%d",&n,&m,&s,&t);
	while(m--){
		int u,v;LL w;
		scanf("%d%d%lld",&u,&v,&w);
		link(u,v,w);link(v,u,0);
	}
	LL ans=0;
	while(bfs())ans+=dfs(s,1e18);
	printf("%lld\n",ans);
	return 0;
}
  • 时间复杂度大概在 \(O(n^2m)\) ,一般能处理 \(1e4-1e5\) 规模的网络
  • \(Dinic\) 处理稠密图非常好用

Updated on 2021/8/11

现在回忆起来,当时还是一个省选抱零的蒟蒻。

  • 更新了代码(最近马蜂)

关于当前弧的定义

当前结点的出边中,最后一次把当前结点的流量增广完的路径,而不是最后一次榨干的路径

这样不会影响全局的正确性,但会大大拉低 \(Dinic\) 的效率。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
template <typename T>
inline T read(){
    T x=0;char ch=getchar();bool fl=false;
    while(!isdigit(ch)){if(ch=='-')fl=true;ch=getchar();}
    while(isdigit(ch)){
        x=(x<<3)+(x<<1)+(ch^48);ch=getchar();
    }
  	return fl?-x:x;
}
const int maxn = 200 + 5 , maxm = 5000 + 10;
#define LL long long
const LL INF = 0x3f3f3f3f3f3f3f3f;
LL c[maxm<<1];
int n,m,s,t;
int head[maxn],cnt=1;
struct edge{
	int to,nxt;
}e[maxm<<1];
inline void link(int u,int v,LL w){
	e[++cnt].to=v;e[cnt].nxt=head[u];head[u]=cnt;c[cnt]=w;
}
inline void add(int u,int v,LL w){
	link(u,v,w);link(v,u,0);
}
#include <queue>
#define read() read<int>()
int de[maxn],now[maxn];
bool bfs(){
	for(int i=1;i<=n;i++)de[i]=0;
	queue<int> q;q.push(s);de[s]=1;
	while(q.size()){
		int u=q.front();q.pop();
		now[u]=head[u];
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].to;
			if(c[i] && !de[v]){//visited
				de[v]=de[u]+1;q.push(v);
			}
		}
	}
	return de[t];
}
LL dfs(int u,LL flow){//当前节点要分配出多少流量
	if(u==t)return flow;
	LL out=0;
	for(int i=now[u];i;i=e[i].nxt){
		now[u]=i;
		int v=e[i].to;
		if(c[i] && de[v]==de[u]+1){
			LL res=dfs(v,min(flow,c[i]));
			out+=res;flow-=res;
			c[i]-=res;c[i^1]+=res;
			if(!flow)break;
		}
	}
	if(out==0)de[u]=0;
	return out;
}
int main(){
	n=read();m=read();s=read();t=read();
	for(int i=1;i<=m;i++){
		int u=read(),v=read();LL w=read<LL>();
		add(u,v,w);
	}
	LL maxflow=0;
	while(bfs())maxflow+=dfs(s,INF);
	printf("%lld\n",maxflow);
	return 0;
}
posted @ 2021-08-12 17:07  ¶凉笙  阅读(47)  评论(0)    收藏  举报