• Note -「网络流 flows」


    基本没有严谨证明。

    Part. 1 概念

    Part. 1-1 流网络

    流网络是一个有向图(不考虑反向边),我们把这个图记为 (G=(V,E))

    其中有两个特殊的点 (s,t),分别成为源点和汇点。

    对于每一个 ((u,v)in E),我们给它两个属性,一个 (c(u,v)),表示这条边的容量;一个 (f(u,v)),表示这条边的流量。

    每一条边都需要满足两个性质:

    • 容量限制,(0le f(u,v)le c(u,v))
    • 流量守恒,(sum_{(x,u)in E}f(x,u)=sum_{u,yin E}f(u,y)),即流进来多少流出去多少。

    (这里的定义是 yxc / 算导 的定义,和网络上大部分的资料不同)

    我们称一个网络的可行流的流量为 (left(sum_{(s,x)in E}f(s,x) ight)-left(sum_{(y,s)in E}f(y,s) ight)),即流出源点的流量减去流入源点的流量。

    一个可行流的方案我们记为 (f),其流量记为 (|f|)

    Part. 1-2 残量网络

    残量网络即把原网络的反向边算进去,把反向边的边集记为 (E'),把残量网络表示为 (G_{f}=(V,Ecup E')),把残量网络的一个可行流方案记为 (f'),同理 (|f'|)(c_{f}(u,v))(f'(u,v))

    这里定义 (f) 之间的加法:

    首先我们知道对于 (G) 的每一个不同的 (f) 而言,其 (G') 是不同的。

    我们定义 (c'(u,v)=egin{cases}c(u,v)-f(u,v),(u,v)in E \ displaystyle f(v,u),(u,v)in E'end{cases})

    那么 (f) 之间的加法是这样定义的:(f'(u,v)=f'(u,v)+f(u,v),f'(v,u)=f'(v,u)-f(u,v))。(定义不太清楚,自行意会吧)

    然后可以证明 (|f+f'|=|f|+|f'|),注意 (|f'|) 可能为负。

    Part. 1-3 增广路

    增广路就是一条从 (s)(t) 的简单路径,满足路上每条边的容量都 (>0)

    Part. 1-4 割

    割是对 (V) 进行的划分。

    首先我们划分出两个集合 (S,T),需满足 (sin S,tin T)(Scup T=V,Scap T=empty)

    我们再定义割的容量:(c(S,T)=sum_{uin S}sum_{vin T}c(u,v))(不考虑反向边)。

    割的流量:(f(S,T)=left(sum_{uin S}sum_{vin T}f(u,v) ight)-left(sum_{uin S}sum_{vin T}f(v,u) ight))

    这里流量和容量的定义不对称,不过 不 影 响。

    可以证明对于任意可行流的任意割,有 (f(S,T)le c(S,T)),也有 (|f|=f(S,T)le c(S,T))

    最小割指容量最小的割。

    Part. 1-5 最大流最小割定理

    • 可行流 (f) 为最大流
    • 对于可行流 (f),其 (G_{f}) 不存在增广路。
    • 存在割 ([S,T])(|f|=c(S,T))

    以上三条可以互相推出。

    最大流等于最小割。

    Part. 2 算法

    Part. 1-1 Edmond-Karp

    不会

    Part. 1-2 Dinic

    先讲讲暴力 FF。

    算了懒得讲了自己看洛谷题解的暴力代码吧。

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    const int N = 10010, E = 200010;
    
    int n, m, s, t;
    LL first[N];
    LL to[E], nxt[E], val[E]/*残余容量*/;
    int cnt = 1;
    //cnt初值1,第一条边的标号为2(二进制10),第二条是3(二进制11) 
    //有啥好处呢?
    //我们加入一条边时,紧接着加入它的反向边(初始容量0) 
    //这两条边的标号就是二进制最后一位不相同,一个0、一个1
    //所以要召唤 p 这条边的反向边,只需用 p ^ 1
    //如果cnt初值为0,就做不到。当然初值-1也可以,略需改动
    
    //关于图中真正的反向边,可能引起顾虑,应该让它们标号相邻? 
    //其实不用。该找到的增广路都会找到的 
    
    bool vis[N];//限制增广路不要重复走点,否则很容易爆栈 
    //兜一大圈走到汇点,还不如直接走到汇点
    
    void addE(int u, int v, LL w) {
    	++cnt;
    	to[cnt] = v;
    	val[cnt] = w;
    	nxt[cnt] = first[u];
    	first[u] = cnt;
    }
    LL dfs(int u, LL flow) {
    	//注意,在走到汇点之前,无法得知这次的流量到底有多少 
    	if (u == t)
    		return flow;//走到汇点才return一个实实在在的流量 
    	
    	vis[u] = true;
    	for (int p = first[u]; p; p = nxt[p]) {
    		int v = to[p];
    		if (val[p] == 0 or vis[v])//无残量,走了也没用 
    			continue;
    		int res = 0;
    		if ((res = dfs(v, min(flow, val[p]))) > 0) {
    			//↑顺着流过去,要受一路上最小容量的限制
    			val[p] -= res;//此边残余容量减小
    			val[p ^ 1] += res;//以后可以顺着反向边收回这些容量,前提是对方有人了 
    			return res;
    		}
    	}
    	return 0;//我与终点根本不连通(依照残量网络),上一个点不要信任我
    }
    int main() {
    	scanf("%d %d %d %d", &n, &m, &s, &t);
    	for (int i = 1; i <= m; ++i) {
    		int u, v; LL w;
    		scanf("%d %d %lld", &u, &v, &w);
    		addE(u, v, w);
    		addE(v, u, 0);//和正向边标号相邻
    		//反向边开始容量为0,表示不允许平白无故走反向边
    		//只有正向边流量过来以后,才提供返还流量的机会
    	}
    	LL res = 0, tot = 0;
    	while (memset(vis, 0, sizeof(vis)) and (res = dfs(s, 1e18/*水库无限*/)) > 0)
    		tot += res;//进行若干回合的增广
    
    	printf("%lld
    ", tot);
    	return 0;
    }
    

    接下来讲讲如何 Dinic。

    其实我不理解 Dinic 比暴力优在哪里

    /* okay | there's been */
    
    #include <cstdio>
    
    namespace mySpace {
    typedef long long LL;
    
    const int MAXN = 200 + 5, MAXM = 5000 + 5;
    
    int rint () {
    	int x = 0, f = 1; char c = getchar ();
    	for ( ; c < '0' || c > '9'; c = getchar () )	f = c == '-' ? -1 : f;
    	for ( ; c >= '0' && c <= '9'; c = getchar () )	x = ( x << 3 ) + ( x << 1 ) + ( c & 15 );
    	return x * f;
    }
    
    template<typename _T> _T MIN ( const _T x, const _T y ) { return x < y ? x : y; }
    
    struct GraphSet {
    	int to, nx;
    	LL wt;
    	GraphSet () : to ( 0 ), nx ( 0 ), wt ( 0 ) {}
    	GraphSet ( const int a, const int b, const LL c ) : to ( a ), nx ( b ), wt ( c ) {}
    } as[MAXM * 2];
    
    int n, m, s, t, bgn[MAXN], cnt = 1, lav[MAXN], ali[MAXN];
    
    void makeEdge ( const int u, const int v, const LL w ) { as[++ cnt] = GraphSet ( v, bgn[u], w ), bgn[u] = cnt; }
    
    bool bfs () {
    	for ( int i = 1; i <= n; ++ i )	lav[i] = 0;
    	int nowl = 1, nowr = 1;
    	ali[1] = s, lav[s] = 1;
    	for ( ; nowl <= nowr; ) {
    		int u = ali[nowl ++];
    		for ( int i = bgn[u]; i; i = as[i].nx ) {
    			int v = as[i].to; LL w = as[i].wt;
    			if ( ! w || lav[v] )	continue;
    			lav[v] = lav[u] + 1, ali[++ nowr] = v;
    		}
    	}
    	return lav[t];
    }
    
    LL dfs ( const int u, LL in ) {
    	if ( u == t )	return in;
    	LL out = 0;
    	for ( int i = bgn[u]; i; i = as[i].nx ) {
    		if ( ! in )	break;
    		int v = as[i].to; LL w = as[i].wt;
    		if ( ! w || lav[v] != lav[u] + 1 )	continue;
    		LL ret = dfs ( v, MIN ( in, w ) );
    		as[i].wt -= ret, as[i ^ 1].wt += ret;
    		in -= ret, out += ret;
    	}
    	if ( ! out )	lav[u] = 0;
    	return out;
    }
    
    LL calcMXflow () {
    	LL res = 0;
    	for ( ; bfs (); res += dfs ( s, 1e18 ) ) ;
    	return res;
    }
    
    void main () {
    	n = rint (), m = rint (), s = rint (), t = rint ();
    	for ( int i = 1; i <= m; ++ i ) {
    		int u = rint (), v = rint (); LL w = rint ();
    		makeEdge ( u, v, w ), makeEdge ( v, u, 0 );
    	}
    	printf ( "%lld
    ", calcMXflow () );
    }
    }
    
    int main () {
    	mySpace :: main ();
    	return 0;
    }
    

    Part. 3 无源汇上下界可行流

    Part. 3-1 概念

    就是每条边的容量从变成了 ([cl(u,v),cr(u,v)])

    Part. 3-2 xxxx

    可以想见通过把容量转化为 ([0,cr(u,v)-cl(uv)]),但这样容量不一定能守恒。

    记每个点的入流量为 (in_{u}),出流为 (out_{u})

    • (in_{u}ge out_{u})

    (S)(u) 连一条容量 (in_{u}-out_{u}) 的边。

    • (in_{u}<out_{u})

    (u)(T) 连一条容量 (out_{u}-in_{u}) 的边。

    Part. 4 有源汇上下界最大流

    Part. 4-1 概念

    有规定的源点汇点了。

    Part. 4-2 xxxx

    图都是从 这里 偷的。这个博客几百年没过图了

    20190824120232582.png

    有源汇后,(s)(t) 不满足流量守恒了,于是从 (s)(t) 连一条容量 ([0,infty]) 的边即可。

    20190824120304124.png

    然后就可以把 (s)(t) 当成普通点处理,重现建立超源超汇,用无源汇上下界可行流的方法跑一遍,看是不是一个可行流,如果是就从 (s)(t) 跑一遍最大流。

    Part. 5 有源汇上下界最小流

    Part. 5-1 概念

    你猜。

    Part. 5-2 xxxx

    首先加上 ((t,s)) 的边跑最大流。

    (d) 是加上的边的流量,删掉加上的边。

    答案是 (d- ext{maxflow}(t,s))

    这里 copy 的。

    Part. 6 多源汇最大流

    Part. 6-1 概念

    你猜。

    Part. 6-2 xxxx

    没啥区别,建超源超汇即可。

  • 相关阅读:
    php中in_array使用注意
    Web 图形可视化 SQL 优化神奇
    Java源码安全审查
    MySQL分库分表方案
    Hystrix 监控数据聚合 Turbine
    idea打包springboot项目没有.original文件
    使用MySQL悲观锁解决并发问题
    使用MySQL乐观锁解决并发问题
    不建议把数据库部署在docker容器内
    Hystrix 监控面板(六)
  • 原文地址:https://www.cnblogs.com/orchid-any/p/14124398.html
Copyright © 2020-2023  润新知