Loading

网络流

1网络流简介

网络流(network-flows)是一种类比水流的解决问题方法,与线性规划密切相关。网络流的理论和应用在不断发展,出现了具有增益的流、多终端流、多商品流以及网络流的分解与合成等新课题。网络流的应用已遍及通讯、运输、电力、工程规划、任务分派、设备更新以及计算机辅助设计等众多领域。

2网络流基本定义

  1. 一个网络\(G=(V,E)\)为一张有向图,图中的每条有向边\((x,y)\in E\)都有一个权值\(c(x,y)\)称为边\((x,y)\)的容量。若\((x,y)\notin E\)\(c(x,y)=1\),其中\(s\in V\) 称为源点,\(t\in V\)称为汇点。

  2. \(f(x,y)\)称为边\((x,y)\)的流量。满足

    1. \(f(x,y)\leq c(x,y)\),即容量限制
    2. \(f(x,y)=-f(y,x)\),即斜对称
    3. \(\sum\limits_{(u,x)\in E}f(u,x)=\sum\limits_{(x,v)\in E}f(x,v),x\not=s,x\not=v\),即流量守恒。
  3. f称为网络的流函数。\(c(x,y)-f(x,y)\)称为边\((x,y)\)的剩余流量。

  4. 若剩余流量大于0,则称这条边为增广路。

  5. 网络中所有的节点以及剩余流量大于0的边构成的子图为残量网络。

  6. \(\sum\limits_{(s,v)\in E}f(s,v)\) 称为整个网络的流量。使其最大的流函数称为网络的最大流。

    大多数题目让我们求到达汇点最大流量。

  7. \(d_x\)为从s到x的最小花费,满足\(d_y=d_x+1\) 的边构成的子图称为分层图,分层图为有向无环图。

可以理解为从源点灌水,每条边能流的水量有一定限制,问到达汇点的水最多为多少。

3求解最大流

3.1Edmonds—Karp算法

显然,若从s到t有一条由增广路组成的路径,即在残量网络上有一条从s到t的路径,那么就可以在更新流量。何时残量网络中不存在增广路,即在残量网络中s无法到达t,说明无法在增光,此时已经是最大流。

对于一条从s到t的增广路,它所能増广的流为该路径上所有边剩余流量的最小值,否则不符合流量守恒定律。

所以我们每次bfs找增广路,存下最小剩余流量和路径,然后更新残量网络。

因为斜对称,所以我们正向边反向边都要处理。

代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ll long long
#define ull unsigned long long
#define N 5010
#define M number
using namespace std;

const ll INF=0x3f3f3f3f;

inline ll Min(ll a,ll b){
	return a>b?b:a;
}

struct Edge{
	ll from,to,cap,flow;
	Edge(ll from,ll to,ll c,ll f) : from(from),to(to),cap(c),flow(f) {}
};

ll n,m,s,t;

struct E_Karp{
	vector<Edge> edge;
	vector<int> G[N];
	ll a[N],p[N];
	
	inline void clear(){
		for(int i=1;i<=n;i++) G[i].clear();
		edge.clear();
	}
	
	inline void addedge(ll from,ll to,ll c){
		edge.push_back(Edge(from,to,c,0));
		edge.push_back(Edge(to,from,0,0));
		ll m=edge.size();
		G[from].push_back(m-2);
		G[to].push_back(m-1);
	}
	
	inline ll Maxflow(ll s,ll t){
		ll flow=0;
		while(1){
			memset(a,0,sizeof(a));
			queue<ll> q;
			q.push(s);
			a[s]=INF;
			while(!q.empty()){
				int x=q.front();q.pop();
				for(int i=0;i<G[x].size();i++){
					Edge& e=edge[G[x][i]];
					if(!a[e.to]&&e.cap>e.flow){
						a[e.to]=Min(a[e.from],e.cap-e.flow);
						p[e.to]=G[x][i];
						q.push(e.to);
					}
				}
				if(a[t]) break;
			}
			if(!a[t]) break;
			for(int u=t;u!=s;u=edge[p[u]].from){
				edge[p[u]].flow+=a[t];
				edge[p[u]^1].flow-=a[t];
			}
			flow+=a[t];
		}
		return flow;
	}
	
};
E_Karp ek;

int main(){
	scanf("%lld%lld%lld%lld",&n,&m,&s,&t);
	for(int i=1;i<=m;i++){
		ll from,to,c;
		scanf("%lld%lld%lld",&from,&to,&c);
		ek.addedge(from,to,c);
	}
	printf("%lld",ek.Maxflow(s,t));
	return 0;
}

时间复杂度\(O(n^2m)\)但实际达不到这个规格,可以处理\(10^3\)~\(10^4\)的网络。

3.2dicnic

显然,EK算法每次bfs只能更新一条增广路,有可以优化的空间。

dicnic算法的主要思想是在分层图上増广流量。

流程:

  1. bfs在残量网络上构造分层图。
  2. 用dfs在分层图上寻找增广路,利用回溯更新流量+剪枝。

剪枝方法:

  1. 増广的流量为0说明这条边以后不可再増广。
  2. 不扩展已经扩展完的边(当前弧优化)

注意当前弧优化一定要放循环内部。

代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ld long double
#define ll long long
#define ull unsigned long long
#define N 2010
#define M 50010
using namespace std;

const ll INF=0x3f3f3f3f;

inline ll read(){
	ll x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}

struct EDGE{
	struct edge{
		int to,next,w,rest;
		inline void intt(int to_,int ne_,int w_,int re_){
			to=to_;next=ne_;w=w_;rest=re_;
		}
	};
	edge li[M];
	int head[N],now[N],tail;
	
	inline EDGE(){
		tail=1;
	}
	
	inline void add(int from,int to,int w){
		li[++tail].intt(to,head[from],w,w);
		head[from]=tail;
		swap(from,to);
		li[++tail].intt(to,head[from],w,0);
		head[from]=tail;
	}
};
EDGE e;

ll n,m,s,t,ans;

int d[N];
queue<int> q;
inline bool bfs(){
	memset(d,0,sizeof(d));
	while(q.size()) q.pop();
	q.push(s);d[s]=1;e.now[s]=e.head[s];
	while(q.size()){
		int top=q.front();q.pop();
		for(int x=e.head[top];x;x=e.li[x].next){
			int to=e.li[x].to,rest=e.li[x].rest;
			if(!rest||d[to]) continue;
			q.push(to);
			e.now[to]=e.head[to];
			d[to]=d[top]+1;
			if(to==t) return 1;
		}
	}
	if(!d[t]) return 0;
	return 1;
}

inline int dicnic(int k,ll flow){
//	printf("k:%d flow:%d\n",k,flow);
	if(k==t) return flow;
	ll rest=flow,x;
	for(x=e.now[k];x&&rest;x=e.li[x].next){
		ll to=e.li[x].to,re=e.li[x].rest;e.now[k]=x;
		if(!re||d[to]!=d[k]+1) continue;
		ll val=dicnic(to,min(rest,re));
		if(!val) d[to]=0;
		e.li[x].rest-=val;
		e.li[x^1].rest+=val;
		rest-=val;
	}
	
	return flow-rest;
}

int main(){
	n=read();m=read();s=read();t=read();
	for(int i=1;i<=m;i++){
		int from=read(),to=read(),w=read();
		e.add(from,to,w);
	}
	ll flow=0;
	while(bfs()){
//		printf("here\n");
		while(flow=dicnic(s,INF)) ans+=flow;
	}
	printf("%lld\n",ans);
	return 0;
}

时间复杂度\(O(n^2m)\)实际远远达不到,可以跑\(10^4\)~\(10^5\)

如果要跑二分图最大匹配,可以为\(O(m\sqrt n)\)

3.3ISAP

dicnic仍然有优化的空间,试想,我们能不能bfs一次呢?

流程:

  1. 我们利用bfs从t反向分层,设为\(h_x\)

  2. 如果\(h_s\ge n\),其中n为节点数,则集团说。

  3. 否则从s开始,在残余网络上沿\(h_u=h_v+1\)的方向前进

  4. 増广

  5. 若流量没有増广完,重贴标签,若拥有当前节点高度的节点数仅一个,结束,否则令\(h_u=\min \{(h_v+1)\times[c(x,y)-f(x,y)>0]\}\)

    如果没有可行的点,令\(h_u=n\),若非源点,退回一步。若是源点,退出。

代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ld long double
#define int long long
#define ull unsigned long long
#define N 2000
#define M 200010
using namespace std;

const int INF=0x3f3f3f3f;

inline int Min(int a,int b){
	return a>b?b:a;
}

inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}

struct edge{
	int to,next,f;
	inline void intt(int to_,int ne_,int f_){
		to=to_;next=ne_;f=f_;
	}
};
edge li[M];
int head[N],tail=1,now[N];

inline void add(int from,int to,int f){
	li[++tail].intt(to,head[from],f);
	head[from]=tail;
	li[++tail].intt(from,head[to],0);
	head[to]=tail;
}

int n,m,s,t,ans;

int h[N],cnth[N];

queue<int> q;
inline void bfs(int t){
	q.push(t);h[t]=0;cnth[0]=1;
	now[t]=head[t];
	while(q.size()){
		int top=q.front();q.pop();
		for(int x=head[top];x;x=li[x].next){
			int to=li[x].to,f=li[x].f;
			if(h[to]||to==t) continue;
			h[to]=h[top]+1;
			cnth[h[to]]++;
			q.push(to);
			now[to]=head[to];
		}
	}
}

inline int isap(int k,int flow){
//	printf("%d\n",k);
	if(k==t) return flow;
	int rest=flow,x,minh=INF;
	for(x=now[k];x&&rest;x=li[x].next){
		int to=li[x].to,re=li[x].f;
		if(re<=0) continue;
		minh=Min(minh,h[to]);
		if(h[to]+1!=h[k]) continue;
		int val=isap(to,Min(rest,re));
		li[x].f-=val;
		li[x^1].f+=val;
		rest-=val;
	}
	if(rest==0) return flow;
	cnth[h[k]]--;
	if(!cnth[h[k]]) h[s]=n+1;
	if(minh==INF) h[k]=n;
	else h[k]=minh+1;
	cnth[h[k]]++;
	return flow-rest;
}

signed main(){
	n=read();m=read();s=read();t=read();
	for(int i=1;i<=m;i++){
		int from=read(),to=read(),f=read();
		add(from,to,f);
	}
	bfs(t);
//	printf("end\n");
	while(h[s]<n) ans+=isap(s,INF);
	printf("%lld\n",ans);
	return 0;
}

4 最小费用最大流

在EK和dicnic中,把dfs改成spfa即可。

EK:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ld long double
#define ll long long
#define ull unsigned long long
#define N 100100
#define M 500100
using namespace std;

const ll INF=0x3f3f3f3f;

inline ll read(){
	ll x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}

inline ll Min(ll a,ll b){
	return a>b?b:a;
}

struct edge{
	ll f,to,next,w;
	inline void intt(ll f_,ll to_,ll ne_,ll w_){
		f=f_;to=to_;next=ne_;w=w_;
	}
};
edge li[M*2];
ll head[N],tail=1;

inline void add(ll from,ll to,ll w,ll c){
	li[++tail].intt(c,to,head[from],w);
	head[from]=tail;
	li[++tail].intt(0,from,head[to],-w);
	head[to]=tail;
}

ll n,m,s,t,ans1,ans2;

ll fl[N],prep[N],pree[N],d[N];
bool vis[N];
queue<ll> q;

inline bool EK(){
//	printf("here\n");
	memset(d,INF,sizeof(d));
	memset(fl,INF,sizeof(fl));
	memset(vis,0,sizeof(vis));
	while(q.size()) q.pop();
	memset(prep,0,sizeof(prep));
	memset(pree,0,sizeof(pree));
	q.push(s);vis[s]=1;d[s]=0;
	while(q.size()){
		ll top=q.front();q.pop();vis[top]=0;
		for(int x=head[top];x;x=li[x].next){
			ll to=li[x].to,rest=li[x].f,w=li[x].w;
			if(rest==0||d[to]<=d[top]+w) continue;
//			printf("x:%d rest:%d to:%d\n",x,rest,to);
			d[to]=d[top]+w;
			fl[to]=Min(fl[top],rest);
			prep[to]=top;pree[to]=x;
			if(!vis[to]) q.push(to),vis[to]=1;
		}
	}
//	printf("d[t]:%lld fl[t]:%lld INF:%lld\n",d[t],fl[t],INF);cin.get();
	if(d[t]<INF&&fl[t]<INF) return 1;
	else return 0;
}

inline void update(){
//	printf("here\n");
	ll now=t,nowe;
	while(prep[now]){
		nowe=pree[now];
		now=prep[now];
//		printf("now:%d nowe:%d\n",now,nowe);
		li[nowe].f-=fl[t];
		li[nowe^1].f+=fl[t];
		ans2+=fl[t]*li[nowe].w;
	}
	ans1+=fl[t];
}

int main(){
//	freopen("P3381_8.in","r",stdin);
//	freopen("P3381_8.out","w",stdout);
	n=read();m=read();s=read();t=read();
	for(int i=1;i<=m;i++){
		ll from=read(),to=read(),c=read(),w=read();
		add(from,to,w,c);
	}
	while(EK()) update();
	printf("%lld %lld\n",ans1,ans2);
	return 0;
}

dicnic:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ld long double
#define ll long long
#define ull unsigned long long
#define N 50100
#define M 50100
using namespace std;

const int INF=0x3f3f3f3f;

inline ll read(){
	ll x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}

struct edge{
	ll f,to,next,w;
	inline void intt(ll f_,ll to_,ll ne_,ll w_){
		f=f_;to=to_;next=ne_;w=w_;
	}
};
edge li[M*2];
ll head[N],tail=1,now[N];

inline void add(ll from,ll to,ll w,ll c){
	li[++tail].intt(c,to,head[from],w);
	head[from]=tail;
	li[++tail].intt(0,from,head[to],-w);
	head[to]=tail;
}

int n,m,s,t,ans1,ans2;

int d[N];
queue<int> q;
bool vis[N];
inline bool spfa(){
	memset(d,INF,sizeof(d));
	memset(vis,0,sizeof(vis));
//	while(q.size()) q.pop();
	q.push(s);d[s]=0;vis[s]=1;now[s]=head[s];
	while(q.size()){
		int top=q.front();q.pop();vis[top]=0;
		for(int x=head[top];x;x=li[x].next){
			int to=li[x].to,w=li[x].w,rest=li[x].f;
//			printf("x:%d to:%d w:%d f:%d\n",x,to,w,rest);
//			printf("d[top]:%d d[to]:%d\n",d[top],d[to]);
			if(!rest||d[to]<=d[top]+w) continue;
			d[to]=d[top]+w;
			now[to]=head[to];
			if(!vis[to]) q.push(to),vis[to]=1;
		}
	}
//	printf("d[t]: %d\n",d[t]);
	if(d[t]>=INF) return 0;
	return 1;
}

inline int dicnic(int k,int flow){
//	printf("here\n");
	if(k==t) return flow;
	int rest=flow,x;
	vis[k]=1;
	for(x=now[k];x&&rest;x=li[x].next){
		int to=li[x].to,f=li[x].f,w=li[x].w;
		if(!f||d[to]!=d[k]+w||(vis[to]&&to!=t)) continue;
		int val=dicnic(to,min(rest,f));
		if(!val) d[to]=0;
		li[x].f-=val;
		li[x^1].f+=val;
		rest-=val;
		ans2+=val*w;
	}
	now[k]=x;
	return flow-rest;
}

int main(){
//	freopen("P3381_8.in","r",stdin);
	n=read();m=read();s=read();t=read();
	for(int i=1;i<=m;i++){
		int from=read(),to=read(),c=read(),w=read();
		add(from,to,w,c);
	}
	while(spfa())
		ans1+=dicnic(s,INF);
	printf("%d %d\n",ans1,ans2);
	return 0;
}

5最小割

最小割即为最大流。

最小割指的是去掉一些边,使s到t无法连通,且去掉边的容量最小。

6技巧

6.1点边转化。

把每一个点拆成入点和出点,从入点到出点的边围护该点原来的信息,连向该点的边连到入点,从该点出去的边从出点出去,无向边互相连即可。

例题:P1345 [USACO5.4]奶牛的电信Telecowmunication

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ld long double
#define ll long long
#define ull unsigned long long
#define N 1100
#define M 7000
using namespace std;

const int INF=0x3f3f3f3f;

inline int Min(int a,int b){
	return a>b?b:a;
}

inline ll read(){
	ll x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}

struct edge{
	int to,next,f;
	inline void intt(int to_,int ne_,int f_){
		to=to_;next=ne_;f=f_;
	}
};
edge li[M<<2];
int head[N<<1],now[N<<1],tail=1;

inline void add(int from,int to,int c){
	li[++tail].intt(to,head[from],c);
	head[from]=tail;
	li[++tail].intt(from,head[to],0);
	head[to]=tail;
}

int n,m,s,t,ans;

int d[N<<1];
queue<int> q;
inline bool bfs(int s){
	memset(d,0,sizeof(d));
	while(q.size()) q.pop();
	d[s]=1;q.push(s);now[s]=head[s];
	while(q.size()){
		int top=q.front();q.pop();
		for(int x=head[top];x;x=li[x].next){
			int to=li[x].to,f=li[x].f;
			if(!f||d[to]) continue;
			d[to]=d[top]+1;
			now[to]=head[to];
			if(to==t) return 1;
			q.push(to);
		}
	}
//	if(d[t]) return 1;
	return 0;
}

inline int dicnic(int k,int flow){
	if(k==t) return flow;
	int rest=flow,x;
	for(x=now[k];x&&rest;x=li[x].next){
		int to=li[x].to,re=li[x].f;
		if(!re||d[to]!=d[k]+1) continue;
		int val=dicnic(to,Min(rest,re));
		if(!val) d[to]=0;
		li[x].f-=val;
		li[x^1].f+=val;
		rest-=val;
	}
	now[k]=x;
	return flow-rest;
}

int main(){
	n=read();m=read();s=read();t=read();s+=n;
	for(int i=1;i<=n;i++) add(i,i+n,1);
	for(int i=1;i<=m;i++){
		int x=read(),y=read();
		add(x+n,y,INF);add(y+n,x,INF);
	}
	while(bfs(s)) ans+=dicnic(s,INF);
	printf("%d\n",ans);
	return 0;
}

6.2二者取一式问题

推荐:https://zhuanlan.zhihu.com/p/123308502

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ld long double
#define ll long long
#define ull unsigned long long
#define N 3000100
#define M 3000100
using namespace std;

const int INF=0x3f3f3f3f;

inline int Min(int a,int b){
	return a>b?b:a;
}

inline ll read(){
	ll x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}

struct edge{
	int to,next,f;
	inline void intt(int to_,int ne_,int f_){
		to=to_;next=ne_;f=f_;
	}
};
edge li[M];
int head[N],tail=1,now[N];

inline int add(int from,int to,int c){
	li[++tail].intt(to,head[from],c);
	head[from]=tail;
	li[++tail].intt(from,head[to],0);
	head[to]=tail;
}

int n,m,s,t,ans,all;

int d[N];
queue<int> q;
inline bool bfs(int s){
	memset(d,0,sizeof(d));
	while(q.size()) q.pop();
	d[s]=1;q.push(s);now[s]=head[s];
	while(q.size()){
		int top=q.front();q.pop();
		for(int x=head[top];x;x=li[x].next){
			int to=li[x].to,f=li[x].f;
			if(!f||d[to]) continue;
			d[to]=d[top]+1;
			now[to]=head[to];
			q.push(to);
//			if(to==t) return 1;
		}
	}
	if(d[t]) return 1;
	return 0;
}

inline int dicnic(int k,int flow){
	if(k==t) return flow;
	int rest=flow,x;
	for(x=now[k];x&&rest;x=li[x].next){
		int to=li[x].to,re=li[x].f;
		if(!re||d[to]!=d[k]+1) continue;
		int val=dicnic(to,Min(rest,re));
		if(!val) d[to]=0;
		li[x].f-=val;
		li[x^1].f+=val;
		rest-=val;
	}
	now[k]=x;
	return flow-rest;
}

int main(){
	n=read();s=n+1;t=n+2;
	for(int i=1;i<=n;i++){
		int x=read();
		add(s,i,x);
		all+=x;
	}
	for(int i=1;i<=n;i++){
		int x=read();
		add(i,t,x);
		all+=x;
	}
	m=read();
	int now=n+2;
	for(int i=1;i<=m;i++){
		int k=read(),ca=read(),cb=read();
		all+=ca;all+=cb;
		now++;add(s,now,ca);now++;add(now,t,cb);
		for(int j=1;j<=k;j++){
			int p=read();
			add(now-1,p,INF);add(p,now,INF);
		}
	}
	while(bfs(s)) ans+=dicnic(s,INF);
	printf("%d\n",all-ans);
	return 0;
}

7引用

  1. 百度百科
  2. 算法进阶训练指南
  3. 趣学算法
posted @ 2021-04-01 09:24  hyl天梦  阅读(310)  评论(0编辑  收藏  举报