网络流笔记

网络流


几句话

网络流的思维方式类似DP

一般来说做题流程为:

  • 题意分析
  • 建图
  • 默写板子

其中建图是关键,类似DP中的转态表示,状态转移

概念

流网络

一张有向图,有两个特殊的点,源点 ( S ) (S) (S),汇点 ( T ) (T) (T)

边权表示该边容纳流量的最大值

G = ( V , E ) G=(V,E) G=(V,E)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XOjPGfB5-1623146274605)(C:\Users\Typedef\Pictures\截图\wll1.png)]

一般认为没有反向边

可行流(f)

满足以下条件:

  • 容量限制 0 ≤ f ( u , v ) ≤ c ( u , v ) 0\leq f(u,v) \leq c(u,v) 0f(u,v)c(u,v)
  • 流量守恒 流进去多少就流出来多少 ( S ) (S) (S) ( T ) (T) (T)除外

源点到汇点的可行流

源点流向其他点的总流量

∣ f ∣ = ∑ ( s , v ) ∈ E f ( s , v ) − ∑ ( v , s ) f ( v , s ) |f|=\sum_{(s,v)\in E}f(s,v)-\sum_{(v,s)}f(v,s) f=(s,v)Ef(s,v)(v,s)f(v,s)

流入放入减流出的

最大流(最大可行流)

可行流中的流量最大值

残留网络

这个概念是对于一条可行流而言的

包含了反向边

某条边的容量 c ′ ( u , v ) c'(u,v) c(u,v):

  • c ( u , v ) − f ( u , v ) c(u,v)-f(u,v) c(u,v)f(u,v), ( u , v ) ∈ E (u,v)\in E (u,v)E
  • f ( u , v ) , ( v , u ) ∈ E f(u,v) ,(v,u)\in E f(u,v),(v,u)E

为什么要建反向边呢?因为一条可行流满流后会影响接下的最优解

原来流网络的一条可行流 f f f加上残留网络的可行流 f ′ f' f也是一条源网络的可行流

∣ f + f ′ ∣ = ∣ f ∣ + ∣ f ′ ∣ |f+f'|=|f|+|f'| f+f=f+f

对于一条可行流,我们在它的残留网络中找到一条增广路

对于一条可行流,若它的残留网络不存在增广路,则该可行流为最大流

一条边,将点集 V V V分为两个子集 S , T S,T S,T

S ∪ T = V S\cup T=V ST=V, S ∩ T = ∅ S\cap T=\varnothing ST=

满足 s ∈ S , t ∈ T s\in S,t\in T sS,tT

点集内部不一定连通

割的容量指的是所有从 S S S指向 T T T的边的容量之和

c ( S , T ) = ∑ u ∈ S ∑ v ∈ T c(S,T)=\sum_{u\in S}\sum_{v\in T} c(S,T)=uSvT

割的流量

f ( S , T ) = ∑ u ∈ S ∑ v ∈ T f ( u , v ) − ∑ v ∈ S ∑ u ∈ T f ( u , v ) f(S,T)=\sum_{u\in S}\sum_{v\in T}f(u,v)- \sum_{v\in S}\sum_{u\in T}f(u,v) f(S,T)=uSvTf(u,v)vSuTf(u,v)

常说的最小割是割的容量

注意,容量不需考虑反向边,流量需要考虑反向边

因此割的容量一定比割的流量小

最大流最小割定理

G = ( V , E ) G=(V,E) G=(V,E)

等价于:

  • f f f是最大流
  • G f 中 不 存 在 增 广 路 G_f中不存在增广路 Gf广
  • 存在一个割 ( S , T ) (S,T) (S,T),使得 ∣ f ∣ = c ( S , T ) |f|=c(S,T) f=c(S,T)

求法

最大流

EK算法

  • 每次在当前残留网络中寻找一条增广路(极为简单的bfs算法)
  • 更新残留网络(极为简单的将 正 向 边 容 量 − k , 反 向 边 容 量 + k 正向边容量-k,反向边容量+k k,+k)

复杂度 O ( n m 2 ) O(nm^2) O(nm2)

然而实际情况是,这个复杂度经常跑不满

因此我们实际能够拿处理的范围是 1 e 4 1e4 1e4~ 1 e 5 1e5 1e5左右

即便如此,该算法的复杂的还是有些高,因此我们更常用 d i n i c dinic dinic或者 I S A P ISAP ISAP

H L P P HLPP HLPP什么的就算了 … \dots 我不会 … \dots

/*************************************************************************
    > File Name: EK.cpp
    > Author: Typedef 
    > Mail: 1815979752@qq.com 
    > Created Time: 2021年05月02日 星期日 16时49分39秒
    > Tags: 
 ************************************************************************/
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
const int N=1314,M=233333;
int n,m,s,t,idx;
int pre[M];
int e[M],h[N],ne[M],f[M];//这里用f表示容量
int q[N],d[N];//这里d表示当前路径上的容量最小值
bool st[N];
void add(int x,int y,int z){
	e[idx]=y,f[idx]=z,ne[idx]=h[x],h[x]=idx++;
	e[idx]=x,f[idx]=0,ne[idx]=h[y],h[y]=idx++;
}
bool bfs(){//宽搜找增广路
	int hh=0,tt=0;
	memset(st,0,sizeof(st));
	q[0]=s,st[s]=1,d[s]=INF;
	while(hh<=tt){
		int u=q[hh++];
		for(int i=h[u];~i;i=ne[i]){
			int v=e[i];
			if(!st[v]&&f[i]){
				st[v]=1;
				pre[v]=i;
				d[v]=min(f[i],d[u]);
				if(v==t) return true;
				q[++tt]=v;
			}
		}
	}
	return false;
}
int EK(){
	int r=0;//总流量
	while(bfs()){
		r+=d[t];
		//这里更新残留网络
		for(int i=t;i!=s;i=e[pre[i]^1])//一个小trick,通过记录的边倒着更新
			f[pre[i]]-=d[t],f[pre[i]^1]+=d[t];
	}
	return r;
}
int main(){
	scanf("%d%d%d%d",&n,&m,&s,&t);
	memset(h,-1,sizeof(h));
	while(m--){
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		add(a,b,c);
	}
	printf("%d\n",EK());
	return 0;
}

dinic算法

复杂度 O ( n 2 m ) O(n^2m) O(n2m)

  • b f s → bfs\to bfs建立分层图并确定有没有增广路
  • d f s → dfs\to dfs找出所有增广路路径

当前弧优化:

  • 枚举到一条点时,依次枚举它的出边找增广路
  • 对于这些出边中满流的边,我们记录下来
  • 当通过其他路径枚举到这个点时,我们就直接跳过满流的边
/*************************************************************************
    > File Name: p3376.cpp
    > Author: Typedef 
    > Mail: 1815979752@qq.com 
    > Created Time: 2021年05月05日 星期三 21时14分57秒
    > Tags: 
 ************************************************************************/
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+7,M=2e5+7,INF=1e8;
int n,m,S,T;
int e[M],ne[M],h[N],idx=0;
int f[M];//f表示当前残留网络中某条边的容量
int q[N],d[N],cur[N];//q是搜索队列,d是分层图的层数,cur是当前弧优化
void add(int a,int b,int c){
	e[idx]=b,ne[idx]=h[a],f[idx]=c,h[a]=idx++;
	e[idx]=a,ne[idx]=h[b],f[idx]=0,h[b]=idx++;
}
bool bfs(){
	int hh=0,tt=0;
	memset(d,-1,sizeof(d));
	q[0]=S,d[S]=0,cur[S]=h[S];
	while(hh<=tt){
		int u=q[hh++];
		for(int i=h[u];~i;i=ne[i]){
			int v=e[i];
			if(d[v]==-1&&f[i]){
				d[v]=d[u]+1;
				cur[v]=h[v];
				if(v==T) return 1;
				q[++tt]=v;
			}
		}
	}
	return 0;
}
int find(int u,int limit){
	if(u==T) return limit;
	int flow=0;
	for(int i=cur[u];~i&&flow<limit;i=ne[i]){
		cur[u]=i;
		int v=e[i];
		if(d[v]==d[u]+1&&f[i]){
			int t=find(v,min(f[i],limit-flow));
			if(!t) d[v]=-1;
			f[i]-=t,f[i^1]+=t,flow+=t;
		}
	}
	return flow;
}
int dinic(){
	int r=0,flow;
	while(bfs()) while(flow=find(S,INF)) r+=flow;
	return r;
}
int main(){
	scanf("%d%d%d%d",&n,&m,&S,&T);
	memset(h,-1,sizeof(h));
	while(m--){
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		add(a,b,c);
	}
	printf("%d\n",dinic());
	return 0;
}

无源汇上下界可行流

每条边都有一个容量上限和容量下限

对于一个流网络 G G G和一条可行流 f f f

我们建立一个新的 G ′ G' G f ′ f' f,使其有源点汇点,并只有容量上界

于是就让每个边的流量减去下界,上界减去下界

然而,对于一个点入度与出度不等的情况,我们无法保证容量守恒

因此我们需要想办法在源点(或汇点)与它连一条边弥补流量

不难发现,为了达成流量守恒,该边必须满流,因此实际上 f ′ f' f G ′ G' G的最大流

源网络的流是可行流 ⟺ \Longleftrightarrow 新流网络上的流是最大流(源点出边满流)

在这里插入图片描述

link

/*************************************************************************
    > File Name: 2188.cpp
    > Author: Typedef 
    > Mail: 1815979752@qq.com 
    > Created Time: 2021年06月08日 星期二 17时23分56秒
    > Tags: 
 ************************************************************************/
#include<bits/stdc++.h>
using namespace std;
const int N=210,M=(10200+N)*2,INF=0x3f3f3f3f;
int n,m,S,T;
int h[N],e[M],f[M],l[M],ne[M],idx;
int q[N],d[N],cur[N],A[N];
void add(int a,int b,int c,int d){
	e[idx]=b,f[idx]=d-c,l[idx]=c,ne[idx]=h[a],h[a]=idx++;
	e[idx]=a,f[idx]=0,ne[idx]=h[b],h[b]=idx++;
}
bool bfs(){
	int hh=0,tt=0;
	memset(d,-1,sizeof(d));
	q[0]=S,d[S]=0,cur[S]=h[S];
	while(hh<=tt){
		int t=q[hh++];
		for(int i=h[t];~i;i=ne[i]){
			int ver=e[i];
			if(d[ver]==-1&&f[i]){
				d[ver]=d[t]+1;
				cur[ver]=h[ver];
				if(ver==T) return true;
				q[++tt]=ver;
			}
		}
	}
	return false;
}
int find(int u,int limit){
	if(u==T) return limit;
	int flow=0;
	for(int i=cur[u];~i&&flow<limit;i=ne[i]){
		cur[u]=i;
		int ver=e[i];
		if(d[ver]==d[u]+1&&f[i]){
			int t=find(ver,min(f[i],limit-flow));
			if(!t) d[ver]=-1;
			f[i]-=t,f[i^1]+=t,flow+=t;
		}
	}
	return flow;
}
int dinic(){
	int r=0,flow;
	while(bfs()) while(flow=find(S,INF)) r+=flow;
	return r;
}
int main(){
	scanf("%d%d",&n,&m);
	memset(h,-1,sizeof(h));
	S=0,T=n+1;
	for(int i=0;i<m;i++){
		int a,b,c,d;
		scanf("%d%d%d%d",&a,&b,&c,&d);
		add(a,b,c,d);
		A[a]-=c,A[b]+=c;
	}
	int tot=0;
	for(int i=1;i<=n;i++)
		if(A[i]>0) add(S,i,0,A[i]),tot+=A[i];
		else if(A[i]<0) add(i,T,0,-A[i]);
	if(dinic()!=tot) puts("NO");
	else{
		puts("YES");
		for(int i=0;i<m*2;i+=2)
			printf("%d\n",f[i^1]+l[i]);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值