网络流算法 学习笔记

网络流

学习博客:网络流基础入门
b站视频:网络流基础入门

Dinic

只学了这一种网络流算法,貌似是最优,最好写的一种;时间复杂度为O( n ∗ m 2 n*m^2 nm2),但是实际上会小于这个,一般可以处理 1 0 4 10^4 104- 1 0 5 10^5 105;

1. 最大流

求最大流个人总结为就是不断找增广路的问题,并且当图中不存在增广路时就达到了最大流。
PS:增广路其实就是 s 到 t 的一条路径,流过这条路径,可以使得当前的流量可以增加。

怎么找增广路呢?其实就是dfs找,但是 Dinic 最关键的是在分层图上面找,所以还要用 bfs
把图转化为分层图;

所以整个算法可以总结为:

不断重复以下步骤,直到残量网络中不能到达 :

  1. 在残量网络上 BFS 求出节点层次,构造分层图。
  2. 在分层图上 DFS 寻找增广路,在回溯时实时更新剩余容量。另外,每个点可以流向多条出边,同时还加入若干剪枝及优化。

特别注意 Dinic 一般要带剪枝优化,复杂度可以降低不少;

模板题:

洛谷·P3376 【模板】网络最大流

模板代码:

#include<bits/stdc++.h>
#define LL long long
#define pa pair<int,int>
#define ls k<<1
#define rs k<<1|1
#define INF 0x3f3f3f3f//大小为1061109567 
using namespace std;
const int N=100100;
const int M=200100;
const LL mod=1e9+7;
const LL inf=9e18;
int head[N],now[N],cnt,n,m,s,t;
struct Node{
	int to,nex;
	LL w;
}edge[M*2];
void add(int p,int q,LL w){
	edge[cnt].w=w,edge[cnt].to=q,edge[cnt].nex=head[p],head[p]=cnt++;
}
int deep[N];
int bfs(){
	for(int i=0;i<=n;i++) deep[i]=0;
	deep[s]=1;
	now[s]=head[s];//当前弧优化 
	queue<int>qu;qu.push(s);
	while(!qu.empty()){
		int p=qu.front();
		qu.pop();
		for(int i=head[p];~i;i=edge[i].nex){
			int q=edge[i].to;
			if(edge[i].w&&!deep[q]){//后续还有流量并且没有访问过 
				deep[q]=deep[p]+1;
				now[q]=head[q];//当前弧优化
				qu.push(q);
			}
		}
	}
	return deep[t];//t点有没有访问到 
}
LL dfs(int p,LL fl){//当前结点和流量 
	if(p==t) return fl;
	LL f=0;
	for(int i=head[p];~i;i=edge[i].nex){//当前弧优化没用  
		now[p]=i;//当前弧优化 
		int q=edge[i].to;
		if(edge[i].w&&deep[q]==deep[p]+1){
			LL x=dfs(q,min(fl,edge[i].w));
			if(x==0) deep[q]=-2;
			else edge[i].w-=x,edge[i^1].w+=x,fl-=x,f+=x;
			if(fl==0) break;
		}
	}
	if(!f) deep[p]=-2;
	return f;
}
int main(){
	memset(head,-1,sizeof(head));
	scanf("%d%d%d%d",&n,&m,&s,&t);
	for(int i=1;i<=m;i++){
		int u,v,w;scanf("%d%d%d",&u,&v,&w);
		add(u,v,w),add(v,u,0);
	}
	LL ans=0;
	while(bfs()) ans+=dfs(s,inf);
	printf("%lld\n",ans);
	return 0;
}

2. 最大流与二分图最大匹配

最大流也可以解决二分图最大匹配问题,以前二分图最大匹配都是用匈牙利算法解决,复杂度为 O ( n ∗ e + m ) O(n*e+m) O(ne+m) ,复杂度比较高;而 Dinic 可以做到 O ( n ∗ s q r t ( m ) ) O(n*sqrt(m)) O(nsqrt(m)) ;降低了复杂度,并且匈牙利算法太过于局限,所以以后都用 Dinic 做二分图最大匹配;

跟Dinic模板的主要区别就是建图,因为是二分图,如果要转化为整个图上来做的话,要建两个源点,S(起点)和T(终点),S 连向左部点,权值为 1,右部点连向T,权值为 1,并且左部和右部点正常连接,权值也为1;

模板代码:

//建图部分
	s=n+m+1,t=s+2;
	for(int i=1;i<=e;i++){
		int u,v;scanf("%d%d",&u,&v);
		if(vis[u][v]) continue;//判重边 
		vis[u][v]=true;
		add(u,v+n,1),add(v+n,u,0);
	}
	for(int i=1;i<=n;i++) add(s,i,1),add(i,s,0);
	for(int i=1;i<=m;i++) add(i+n,t,1),add(t,i+n,0);

3. 最小、最大费用最大流

模板题:洛谷·P3381 【模板】最小费用最大流

这个就是在原先最大流的基础上,每条边不仅有了容量限制,还加了个单位费用,所以该图中总花费最小的最大流被称为最小费用最大流,总花费最大的最大流被称为最大费用最大流。

代码就是在原先 Dinic 基础上进行修改,主要是把 bfs 改为 spfa, 因为原先是无权图的分层图,可以直接bfs,现在变为有权图的分层图,也就是求最短路,必须改为spfa;如果是最大费用最大流,就是 spfa 求最长路;

为啥用 spfa 不用 dij 呢?因为这里建图时还有反向加入负边,所以 spfa 更好处理负权值的边;

模板代码:

#include<bits/stdc++.h>
#define LL long long
#define pa pair<int,int>
#define ls k<<1
#define rs k<<1|1
#define INF 0x3f3f3f3f//大小为1061109567 
using namespace std;
const int N=100100;
const int M=200100;
const LL mod=1e9+7;
const LL inf=9e18;
int head[N],now[N],cnt,n,m,s,t;
LL ans1,ans2;//最大流,最小费用最大流 
struct Node{
	int to,nex;LL w,fe;
}edge[M*2];
void add(int p,int q,LL w,LL fe){
	edge[cnt].fe=fe,edge[cnt].w=w,edge[cnt].to=q,edge[cnt].nex=head[p],head[p]=cnt++;
}
LL dis[N];//最短路 
bool vis1[N],vis2[N];//spfa标记数组 
int spfa(){
	for(int i=0;i<=n;i++) dis[i]=inf,vis1[i]=false;
	dis[s]=0;
	now[s]=head[s];//当前弧优化 
	queue<int>qu;qu.push(s);
	while(!qu.empty()){
		int p=qu.front();
		qu.pop();vis1[p]=false;
		for(int i=head[p];~i;i=edge[i].nex){
			int q=edge[i].to;
			if(edge[i].w&&dis[q]>dis[p]+edge[i].fe){//保证还有流量,并且是最短路 
				dis[q]=dis[p]+edge[i].fe;
				if(!vis1[q]){
					vis1[q]=true;
					qu.push(q);
				}
				now[q]=head[q];//当前弧优化 
			}
		}
	}
	if(dis[t]==inf) return 0;//t点有没有访问到 
	else return 1;
}
LL dfs(int p,LL fl){//当前结点和流量 
	if(p==t) return fl;
	LL f=0;
	vis2[p]=true;
	for(int i=now[p];~i;i=edge[i].nex){//当前弧优化没用 
		now[p]=i;//当前弧优化 
		int q=edge[i].to;
		if(edge[i].w&&dis[q]==dis[p]+edge[i].fe&&!vis2[q]){
			LL x=dfs(q,min(fl,edge[i].w));
			if(x==0) dis[q]=inf;
			else ans2+=x*edge[i].fe,edge[i].w-=x,edge[i^1].w+=x,fl-=x,f+=x;
			if(fl==0) break;
		}
	}
	vis2[p]=false;//回溯 
	if(!f) dis[p]=inf;
	return f;
}
int main(){
	memset(head,-1,sizeof(head));
	scanf("%d%d%d%d",&n,&m,&s,&t);
	for(int i=1;i<=m;i++){
		int u,v;LL w,fe;scanf("%d%d%lld%lld",&u,&v,&w,&fe);
		add(u,v,w,fe),add(v,u,0,-fe);
	}
	while(spfa()) ans1+=dfs(s,inf);
	printf("%lld %lld\n",ans1,ans2);
	return 0;
}

4. 最大费用最大流与二分图带权最大匹配

类似与最大流解决二分图最大匹配问题,每条边的权值就是它的费用;以前写二分图带权最大匹配都是用的 KM 算法,复杂度为 O ( n 3 ) O(n^3) O(n3) ,Dinic 算法复杂度貌似也差不多,两种都可以;

因为是最大费用最大流,所以说是求最长路,一般的写法是边权值取反,然后跑最小费用最大流,最后答案取反即可;

比较模板的题: 洛谷·P2045 方格取数加强版

这题难在建模:

  1. 拆点建边,每个格子 ( x , y ) (x,y) (x,y) 拆成一个入点和一个出点。然后入点向出点连一条有向边,容量为 1,费用为 a[x][y];然后出点向入点连一条有向边,容量为 inf,费用为 0;
  2. 格子 ( x , y ) (x,y) (x,y)的出点向对应的 ( x + 1 , y ) (x+1,y) (x+1,y) ( x , y + 1 ) (x,y+1) (x,y+1)的入点连一条有向边,容量为 inf,费用为 0;
  3. 特别注意要加一个源点 S 和一个源点 T,S 连向第一个格子的入点,容量为 k,费用为0,不再是 inf,特别注意;最后一个格子的出点连 T,容量也为 k,费用为0。最后跑最大费用最大流即可;

模板代码:

#include<bits/stdc++.h>
#define LL long long
#define pa pair<int,int>
#define ls k<<1
#define rs k<<1|1
#define INF 0x3f3f3f3f//大小为1061109567 
using namespace std;
const int N=100100;
const int M=200100;
const LL mod=1e9+7;
const LL Inf=9e18;
const int inf=2e9;
int head[N],now[N],cnt,n,k,s,t,tot,a[100][100],ans;
pa b[100][100];
struct Node{
	int to,nex,w,fe;
}edge[M*2];
void add(int p,int q,int w,int fe){
	edge[cnt].fe=fe,edge[cnt].w=w,edge[cnt].to=q,edge[cnt].nex=head[p],head[p]=cnt++;
}
int dis[N];//最短路 
bool vis1[N],vis2[N];//spfa标记数组,dfs标记数组 
int spfa(){
	for(int i=0;i<=10*tot;i++) dis[i]=inf,vis1[i]=false;
	dis[s]=0;
	now[s]=head[s];//当前弧优化 
	queue<int>qu;qu.push(s);
	while(!qu.empty()){
		int p=qu.front();
		qu.pop();vis1[p]=false;
		for(int i=head[p];~i;i=edge[i].nex){
			int q=edge[i].to;
			if(edge[i].w>0&&dis[q]>dis[p]+edge[i].fe){//保证还有流量,并且是最长路 
				dis[q]=dis[p]+edge[i].fe;
				if(!vis1[q]){
					vis1[q]=true;
					qu.push(q);
				}
				now[q]=head[q];//当前弧优化 
			}
		}
	}
	if(dis[t]==inf) return 0;//t点有没有访问到 
	else return 1;
}
int dfs(int p,int fl){//当前结点和流量 
	if(p==t) return fl;
	int f=0;
	vis2[p]=true;
	for(int i=head[p];~i;i=edge[i].nex){//当前弧优化没用 
		now[p]=i;//当前弧优化 
		int q=edge[i].to;
		if(edge[i].w&&dis[q]==dis[p]+edge[i].fe&&!vis2[q]){
			int x=dfs(q,min(fl,edge[i].w));
			if(x==0) dis[q]=inf;
			else ans+=x*edge[i].fe,edge[i].w-=x,edge[i^1].w+=x,fl-=x,f+=x;
			if(fl==0) break;
		}
	}
	vis2[p]=false;//回溯 
	if(!f) dis[p]=inf;
	return f;
}
int main(){
	memset(head,-1,sizeof(head));
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			scanf("%d",&a[i][j]);
			b[i][j].first=++tot,b[i][j].second=++tot;
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			add(b[i][j].first,b[i][j].second,1,-a[i][j]);//反边+负权+容量清0 
			add(b[i][j].second,b[i][j].first,0,a[i][j]);
			add(b[i][j].first,b[i][j].second,inf,0);
			add(b[i][j].second,b[i][j].first,0,0);
			if(i+1<=n){
				add(b[i][j].second,b[i+1][j].first,inf,0);
				add(b[i+1][j].first,b[i][j].second,0,0);
			}
			if(j+1<=n){
				add(b[i][j].second,b[i][j+1].first,inf,0);
				add(b[i][j+1].first,b[i][j].second,0,0);
			}
		} 
	}
	s=0,t=2*tot+1;
	add(s,b[1][1].first,k,0),add(b[1][1].first,s,0,0);
	add(b[n][n].second,t,k,0),add(k,b[n][n].second,0,0);
	while(spfa()) dfs(s,inf);
	printf("%d\n",-ans);
	return 0;
}

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值