模板:网络流(Dinic算法)

1.网络最大流

洛谷P3376

题目描述

在这里插入图片描述
给出一个网络图,以及其源点和汇点,求出其网络最大流。

解析

网络流的思想就是在原有的基础上不断进行增广
基于一个贪心的思路,先bfs判断是否存在增广路,再通过dfs增广

反悔边

但显然贪心会出错(比如当前终点通过其他点来使用会更优时)
所以就引入了反悔边
就是与所给边方向相反,一开始容量为0
增广使用边时除了把改变的边容量减去流量,再把反向边加上同样的流量即可
这样以后在必要时,就可以通过走反悔边的方式撤销之前的错误决策
举个例子
在这里插入图片描述
从S到T
如果贪心的走。先让2的10给了4
但实际上应该是2给5,3给4最优
那么我们就在2给4时使4到2的边容量从0加到10
这样在计算3这个点时就会顺着1-3-4-2-5-T走到终点
2-4与4-2流量都为10,等价于把一开始2到4这步操作撤销了

分层(避免环流)

为了避免dfs环流死循环的情况,我们要把这个图先分一下层
比如上图就是:

1层:S
2层:2 3
3层:4 5
4层 T

强制让dfs只能走到层数+1的点

时间优化

只是这么写的话会T掉一个点(也可能是我太菜常数太大。。。
考虑能否优化

我们发现,每次bfs之后的多次dfs增广中,这个图的分层结构是固定的
每个点尝试走的出边可能有一些在之前几次dfs已经用过,再枚举时其实已经得不到更多的流了
所以我们每次dfs时到每一个点就从上次dfs枚举到的出边开始枚举就行了
但注意,重新bfs后,图的结构改变,之前没用的边可能又能有流了
所以要重新从头枚举

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=300;
const int M=15500;

struct node{
	int to,nxt;
	ll cap;
}p[M];
int fi[N],cnt=-1;
void addline(int x,int y,ll v){
	p[++cnt]=(node){y,fi[x],v};
	fi[x]=cnt;
}

int n,m,s,t;
int a,b,c,d;

int dis[N];
int cur[N];
void print(){for(int i=1;i<=n;i++) printf("%d ",dis[i]);printf("\n");}
int bfs(){
	queue<int>q;
	q.push(s);
	memset(dis,0,sizeof(dis));
	dis[s]=1;
	while (!q.empty()){
		int x = q.front();q.pop();
		for (int i = cur[x]= fi[x];~i;i = p[i].nxt){
			int to = p[i].to;
			if (dis[to]||!p[i].cap) continue;
			dis[to] = dis[x] + 1;
			q.push(to);
		}
	}
	return dis[t];
}
ll dfs(int x,ll lim){
	if(x==t||!lim) return lim;
//	printf("%d\n",x);
	ll res=0;
	for(int &i=cur[x];~i&&lim;i=p[i].nxt){
		int to=p[i].to;
		if(dis[to]!=dis[x]+1) continue;
		ll f=dfs(to,min(p[i].cap,lim));
		lim-=f;
		res+=f;
		p[i].cap-=f;
		p[i^1].cap+=f;
	}
	return res;
}
ll dinic(){
	ll ans=0,flow;
	while(bfs()){
		while(flow=dfs(s,2e15)) ans+=flow;
	}
	return ans;
}
int main(){
	memset(fi,-1,sizeof(fi));
	scanf("%d%d",&m,&n);
	s=1;t=n;
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&a,&b,&c);
		addline(a,b,c);
		addline(b,a,0);
	}
	printf("%lld",dinic());
}
/*
4 5 4 3
4 2 30
4 3 20
2 3 20
2 1 30
1 3 40
*/

2.费用流

洛谷P3381

描述

和最大流类似
只是每条边加了一个单位流的费用
求在最大流的前提下的最小费用方案

解析

上一题搞完这题就好多了
由于要求最小费用,把增广的bfs改为spfa跑费用的最短路
更新时记录路径
寻找路径上流量最小的值
然后累加费用即可

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=5500;
const int M=55000;

struct node{
	int to,nxt;
	ll cap,v;
}p[M<<1];
int fi[N],cnt=-1;
void addline(int x,int y,ll w,ll v){
	p[++cnt]=(node){y,fi[x],w,v};
	fi[x]=cnt;
}

int n,m,s,t;
int a,b,c,d;

int dis[N];
int pre[N],from[N];
int vis[N];
bool spfa(){
	pre[t]=0;
	memset(dis,63,sizeof(dis));
	memset(vis,0,sizeof(vis));
	queue<int>q;
	q.push(s);
	vis[s]=1;
	dis[s]=0;
	while(!q.empty()){
		int now=q.front();q.pop();
		vis[now]=0;
//		printf("now=%d");
		for(int i=fi[now];~i;i=p[i].nxt){
			int to=p[i].to;
			if(p[i].cap==0) continue;
			if(dis[to]>dis[now]+p[i].v){
				dis[to]=dis[now]+p[i].v;
				pre[to]=now;from[to]=i;
				if(!vis[to]){
					vis[to]=1;q.push(to);
				}
			}
		}
	}
	return pre[t];
}
void dinic(){
	ll flow=0,v=0,tmp;
	while(spfa()){
		tmp=2e15;
		for(int i=t;i!=s;i=pre[i]) tmp=min(tmp,p[from[i]].cap);
		flow+=tmp;
		for(int i=t;i!=s;i=pre[i]){
			v+=tmp*p[from[i]].v;
			p[from[i]].cap-=tmp;
			p[from[i]^1].cap+=tmp;
		}
	}
	printf("%lld %lld",flow,v);
	return;
}
int main(){
	memset(fi,-1,sizeof(fi));
	scanf("%d%d%d%d",&n,&m,&s,&t);
	for(int i=1;i<=m;i++){
		scanf("%d%d%d%d",&a,&b,&c,&d);
		addline(a,b,c,d);
		addline(b,a,0,-d);
	}
	dinic();
	return 0;
}
/*
4 5 4 3
4 2 30
4 3 20
2 3 20
2 1 30
1 3 40
*/
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值