网络流

网络流

基础知识

网络

在一个有向图里,有一个源点有一个汇点,每条边有一个容量,这种图叫做网络。

流量

容量:每条边都有一个容量(水管的最大水流容量)常用c(a,b)表示

源点:出发点

汇点:结束点

流:一个合法解称作一个流,也就是一条可以从源点到汇点的一条合法路径。

流量:每条边各自被经过的次数称作其流量,最终收集的总数为整个流的流量.常用f(a,b)表示

容量限制:每条边的流量不超过其容量(水管会爆的)。

流量平衡:对于除源点和汇点以外的点来说,其流入量一定等于流出量

即f(a,c)+f(b,c)=f(c,d);

对于任意除了源点和汇点外的点u,都有\(\sum\)f(i,u)=\(\sum\)f(u,j)

网络流的割:是网络中顶点的一个划分,把所有顶点划分成两个顶点集合S和T,其中源点s属于S,汇点t属于T,记作CUT(S,T)。

割的割边:如果一条弧的两个顶点一个属于顶点集S一个属于顶点集T,该弧为割CUT(S,T)的一条割边。

最大流==最小割

费用流

给定一个网络,每条边除了容量限制c(u,v),还有一个单位流量费用w(u,v).当(u,v)的流量为f(u,v)时,需要花费f(u,v)*w(u,v)的费用,其中w也满足w(u,v)=-w(v,u)。

则该网络中总花费最小的最大流称为最小费用最大流,即在最大化\(\sum\)(s,v)\(\in\)Ef(s,v)的前提下最小化\(\sum\)(u,v)\(\in\)Ef(u,v)*w(u,v)

最大流

找到一条从源点S到汇点T的路,使得流经的流量最大,这条路叫做最大流。

为了解决最大流问题,我们需要引入一个概念:增广路。

增广路

在原图G中若一条从源点S到汇点T上所有边的剩余容量都大于0(注意:要严格大于0)这条路被成为增广路(Augmenting Path)

或者说:在残存网络Gf中,有一条从源点到汇点的路径被称为增广路

反向边

在最大流算法中,我们往往引入反向流概念,使算法可以后悔。即在算法中需要回溯,但是一旦回溯,时间复杂度就会变成指数级,所以一些聪明的前辈们就建立了反向边。

反向边满足:c+(i,j) + c-(i,j) = c(i,j)

Edmond-Karp 动能算法(EK 算法)

算法步骤:

1.在图上找到一条从源点到汇点的路径(称为‘增广路’)。

2.去增广路上的残量最小值v。(也就是流过的路径中流量最小的那一个)

3.将答案加上v。

4,.将增广路上所有边的残量减去v,反向边的残量加上v。(满足反向边条件)

重复上边4个步骤直到找不到增广路为止。

例题:P3376 【模板】网络最大流

https://www.luogu.com.cn/problem/P3376

代码实现:

#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
using namespace std;
const int N = 5007;
const int INF = 0x3f3f3f3f; 
struct edge{
	int from,to;
	ll cap,flow;
	edge(int u,int v,ll c,ll f):from(u),to(v),cap(c),flow(f){};
};
struct EK{
	int n,m;//这个图有n个点,m条边 
	vector<edge> e;//保留所有边的信息 
	vector<int> G[N];//G[i][j]保留节点i的第j条边在e数组里的编号 
	ll a_now[N];//每个点目前的水流量 
	int pre[N];//节点i的前一个节点的编号
	void init(int n){
		for(int i = 0; i <= n; i++)G[i].clear();
		e.clear();
	} 
	void add(int u,int v,ll c){
		e.push_back(edge(u,v,c,0));//正向边 
		e.push_back(edge(v,u,0,0));//反向边
		m = e.size();
		G[u].push_back(m - 2);
		G[v].push_back(m - 1); 
	}
	ll Maxflow(int s,int t){
		ll flow=0;
		while(1){
			memset(a_now,0,sizeof(a_now));//最初每个点的水流量都是0 
			queue<int>Q;//BFS 
			Q.push(s);
			a_now[s] = INF; 
			while(!Q.empty()){
				int x = Q.front();//目前水流到的节点;
				Q.pop();//出队
				for(int i = 0;i < G[x].size(); i++) {
					edge now=e[G[x][i]];//G[x][i]是点x到i的边的编号,now表示点x到i的所有信息
					if(!a_now[now.to] && now.cap > now.flow){//点i未流到并且这条路没流满 
						pre[now.to] = G[x][i];
						a_now[now.to] = min(a_now[x], now.cap - now.flow);
						//流到下一点的水量为上一点的水量或者路径上还可以流的最大流量,这两者取最小值 
						Q.push(now.to);//下一个节点入队 
					} 
				}
				if(a_now[t])//已经流到终点,退出寻找 
					break; 
			}
			if(!a_now[t])//如果所有路都已经试过,水不能流到终点,已经是最大流 
				break;
			for(int u = t; u != s; u = e[pre[u]].from){//反向记录路径
				e[pre[u]].flow += a_now[t];//路径上所有正向边的流量增加流到终点的流量
				e[pre[u] ^ 1].flow -= a_now[t];//路径上所有反向边的流量减少流到终点的流量
			}
			flow += a_now[t]; 
		}
		return flow;
	}
};
int main(){
	int n,m;
	EK ek;
	while(~scanf("%d%d", &n, &m)){
		ek.init(n);
		int s,t;
		scanf("%d%d",&s,&t);
		int u,v;
		ll w;
		while(m--){
			scanf("%d%d%lld",&u,&v,&w);
			ek.add(u,v,w);
		}
		printf("%lld\n",ek.Maxflow(s,t));
	}
	return 0;
} 
posted @ 2021-02-01 21:09  Shayndel  阅读(89)  评论(0编辑  收藏  举报