zoukankan      html  css  js  c++  java
  • 网络流

    网络流

    基础知识

    网络

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

    流量

    容量:每条边都有一个容量(水管的最大水流容量)常用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
    ",ek.Maxflow(s,t));
    	}
    	return 0;
    } 
    
  • 相关阅读:
    Linux 设备驱动 Edition 3(中文版)
    内核和用户空间共享内存的实现例程proc和mmap
    mmap的详细使用(用户空间)
    使用 I/O 内存from LDD3
    关于strcpy、memset、memcpy的使用详解
    get_free_page 和其友
    Linux调试技术介绍
    关于信息时代的学习
    [恢]hdu 1196
    [恢]hdu 1157
  • 原文地址:https://www.cnblogs.com/Shayndel/p/14359036.html
Copyright © 2011-2022 走看看