[Poj 1459] 网络流(一) {基本概念与算法}

{

凸包的内容还欠整理

先来侃侃一个月以前就想写写的网络流

本文介绍网络流 网络流的算法 及其应用

这些问题没事想想还是很有意思的

}

==================================================================================

一.网络流:流&网络&

1.网络流问题(NetWork Flow Problem):

给定指定的一个有向图,其中有两个特殊的点源S(Sources)和汇T(Sinks),每条边有指定的容量(Capacity),求满足条件的从S到T的最大流(MaxFlow).

The network flow problem considers a graph G with a set of sources S and sinks T and for which each edge has an assigned capacity (weight), and then asks to find the maximum flow that can be routed from S to T while respecting the given edge capacities.

http://mathworld.wolfram.com/NetworkFlow.html

2011030323472858.jpg

下面给出一个通俗点的解释

(下文基本避开形式化的证明 基本都用此类描述叙述)

好比你家是汇 自来水厂(有需要的同学可以把自来水厂当成银行之类 以下类似)是源

然后自来水厂和你家之间修了很多条水管子接在一起 水管子规格不一 有的容量大 有的容量小

然后问自来水厂开闸放水 你家收到水的最大流量是多少

如果自来水厂停水了 你家那的流量就是0 当然不是最大的流量

但是你给自来水厂交了100w美金 自来水厂拼命水管里通水 但是你家的流量也就那么多不变了 这时就达到了最大流

-------------------------------------------------------------------------------------------------------------

2.三个基本的性质:

如果 C代表每条边的容量 F代表每条边的流量

一个显然的实事是F小于等于C 不然水管子就爆了

这就是网络流的第一条性质 容量限制(Capacity Constraints):F<x,y> ≤ C<x,y>

再考虑节点任意一个节点 流入量总是等于流出的量 否则就会蓄水(爆炸危险...)或者平白无故多出水(有地下水涌出?)

这是第二条性质 流量守恒(Flow Conservation):Σ F<v,x> = Σ F<x,u>

当然源和汇不用满足流量守恒 我们不用去关心自来水厂的水是河里的 还是江里的

(插播广告: 节约水资源 人人有责!)

最后一个不是很显然的性质 是斜对称性(Skew Symmetry): F<x,y> = - F<y,x>

这其实是完善的网络流理论不可缺少的 就好比中学物理里用正负数来定义一维的位移一样

百米起点到百米终点的位移是100m的话 那么终点到起点的位移就是-100m

同样的 x向y流了F的流 y就向x流了-F的流

-------------------------------------------------------------------------------------------------------------

3.容量网络&流量网络&残留网络:

网络就是有源汇的有向图 关于什么就是指边权的含义是什么

容量网络就是关于容量的网络 基本是不改变的(极少数问题需要变动)

2011030400015287.png

流量网络就是关于流量的网络 在求解问题的过程中

通常在不断的改变 但是总是满足上述个性质

调整到最后就是最大流网络 同时也可以得到最大流值

2011030400020663.png

残留网络往往概括了容量网络和流量网络 是最为常用的

残留网络=容量网络-流量网络

这个等式是始终成立的 残留值当流量值为负时甚至会大于容量值

流量值为什么会为负?有正必有负,记住斜对称性!

2011030400041780.png

-------------------------------------------------------------------------------------------------------------

4.割&割集:

无向图的割集(Cut Set):C[A,B]是将图G分为A和B两个点集 A和B之间的边的全集

A set of edges of a graph which, if removed (or "cut"), disconnects the graph (i.e., forms a disconnected graph).

http://mathworld.wolfram.com/CutSet.html

网络的割集:C[S,T]是将网络G分为s和t两部分点集 S属于s且T属于t 从S到T的边的全集

带权图的割(Cut)就是割集中边或者有向边的权和

Given a weighted, undirected graph G=(V,E) and a graphical partition of V into two sets A and B, the cut of G with respect to A and B is defined as cut(A,B)=sum_(i in A,j in B)W(i,j),where W(i,j) denotes the weight for the edge connecting vertices i and j. The weight of the cut is the sum of weights of edges crossing the cut.

http://mathworld.wolfram.com/Cut.html

2011030411350167.png

通俗的理解一下:

割集好比是一个恐怖分子 把你家和自来水厂之间的水管网络砍断了一些

然后自来水厂无论怎么放水 水都只能从水管断口哗哗流走了 你家就停水了

(插播广告: 节约水资源 人人有责!)

割的大小应该是恐怖分子应该关心的事 毕竟细管子好割一些

而最小割花的力气最小

==================================================================================

二.计算最大流的基本算法

那么怎么求出一个网络的最大流呢?

这里介绍一个最简单的算法:Edmonds-Karp算法最短路径增广算法 简称EK算法

EK算法基于一个基本的方法:Ford-Fulkerson方法增广路方法 简称FF方法

增广路方法是很多网络流算法的基础 一般都在残留网络中实现

其思路是每次找出一条从源到汇的能够增加流的路径 调整流值和残留网络 不断调整直到没有增广路为止

FF方法的基础是增广路定理(Augmenting Path Theorem):网络达到最大流当且仅当残留网络中没有增广路

证明略 这个定理应该能够接受的吧

EK算法就是不断的找最短路 找的方法就是每次找一条边数最少的增广 也就是最短路径增广

这样就产生了三个问题:

-------------------------------------------------------------------------------------------------------------

1.最多要增广多少次?

可以证明 最多O(VE)次增广 可以达到最大流 证明略

2.如何找到一条增广路?

先明确什么是增广路 增广路是这样一条从s到t的路径 路径上每条边残留容量都为正

把残留容量为正的边设为可行的边 那么我们就可以用简单的BFS得到边数最少的增广路

3.如何增广?

BFS得到增广路之后 这条增广路能够增广的流值 是路径上最小残留容量边决定的

把这个最小残留容量MinCap值加到最大流值Flow上 同时路径上每条边的残留容量值减去MinCap

最后 路径上每条边的反向边残留容量值要加上MinCap 为什么? 下面会具体解释

-------------------------------------------------------------------------------------------------------------

这样每次增广的复杂度为O(E) EK算法的总复杂度就是O(VE^2)

事实上 大多数网络的增广次数很少 EK算法能处理绝大多数问题

平均意义下增广路算法都是很快的

增广路算法好比是自来水公司不断的往水管网里一条一条的通水

上面还遗留了一个反向边的问题: 为什么增广路径上每条边的反向边残留容量值要加上MinCap?

因为斜对称性! 由于残留网络=容量网络-流量网络

容量网络不改变的情况下

由于增广好比给增广路上通了一条流 路径说所有边流量加MinCap

流量网络中路径上边的流量加MinCap 反向边流量减去MinCap

相对应的残留网络就发生相反的改变

这样我们就完成了EK算法 具体实现可以用邻接表存图 也可以用邻接矩阵存图

邻接表存图 由于流量同时存在于边与反向边 为了方便求取反向边 建图把一对互为反向边的边建在一起

代码很简单 最好自己实现一下

ContractedBlock.gif ExpandedBlockStart.gif EK
 
   
const maxn = 1000 ;
oo
= maxlongint;
var a,b,c,n,m,h,t,i,min,ans:longint;
g:
array [ 1 ..maxn, 1 ..maxn] of longint;
q,p,prev:
array [ 1 ..maxn] of longint;
flag:boolean;
begin
assign(input,
' Ditch.in ' ); reset(input);
assign(output,
' Ditch.out ' ); rewrite(output);
while not eof do
begin
readln(m,n);
fillchar(g,sizeof(g),
0 );
for i: = 1 to m do
begin
readln(a,b,c);
g[a,b]:
= g[a,b] + c;
end ;
ans:
= 0 ;
while true do
begin
h:
= 1 ; t: = 1 ;
fillchar(p,sizeof(p),
0 );
q[
1 ]: = 1 ; p[ 1 ]: = 1 ;
flag:
= false;
while h <= t do
begin
for i: = 1 to n do
if (p[i] = 0 ) and (g[q[h],i] > 0 )
then begin
inc(t); q[t]:
= i;
p[i]:
= 1 ; prev[t]: = h;
if q[t] = n
then begin
flag:
= true;
break;
end ;
end ;
if flag then break;
inc(h);
end ;
if not flag
then break;
i:
= t; min: = oo;
while q[i] <> 1 do
begin
if g[q[prev[i]],q[i]] < min
then min: = g[q[prev[i]],q[i]];
i:
= prev[i];
end ;
i:
= t;
while q[i] <> 1 do
begin
g[q[prev[i]],q[i]]:
= g[q[prev[i]],q[i]] - min;
g[q[i],q[prev[i]]]:
= g[q[i],q[prev[i]]] + min;
i:
= prev[i];
end ;
ans:
= ans + min;
end ;
writeln(ans);
end ;
close(input); close(output);
end .

看一个具体的增广路算法的例子吧

2011030413212039.png

==================================================================================

三.最大流最小割定理

下面介绍网络流理论中一个最为重要的定理

最大流最小割定理(Maximum Flow, Minimum Cut Theorem):网络的最大流等于最小割

The maximum flow between vertices v_i and v_j in a graph G is exactly the weight of the smallest set of edges to disconnect G with v_i and v_j in different components.

http://mathworld.wolfram.com/MaximumFlowMinimumCutTheorem.html

具体的证明分三部分

1.任意一个流都小于等于任意一个割

这个很好理解 自来水公司随便给你家通点水 构成一个流

恐怖分子随便砍几刀 砍出一个割

由于容量限制 每一根的被砍的水管子流出的水流量都小于管子的容量

每一根被砍的水管的水本来都要到你家的 现在流到外面 加起来得到的流量还是等于原来的流

管子的容量加起来就是割 所以流小于等于割

由于上面的流和割都是任意构造的 所以任意一个流小于任意一个割

2.构造出一个流等于一个割

当达到最大流时 根据增广路定理

残留网络中s到t已经没有通路了 否则还能继续增广

我们把s能到的的点集设为S 不能到的点集为T

构造出一个割集C[S,T] S到T的边必然满流 否则就能继续增广

这些满流边的流量和就是当前的流即最大流

把这些满流边作为割 就构造出了一个和最大流相等的割

3.最大流等于最小割

设相等的流和割分别为Fm和Cm

则因为任意一个流小于等于任意一个割

任意F≤Fm=Cm≤任意C

定理说明完成

==================================================================================

四.简单的应用

Poj 1459是一个很典型的网络流应用

把电流想象成水流

2011030413333680.jpg

http://poj.org/problem?id=1459

注意把多源多汇转化为单源单汇即可利用EK算法解决问题

网络流的应用还有很多 化归的思想是网络流最具魅力的地方

代码如下:

ContractedBlock.gif ExpandedBlockStart.gif PowerNet
 
   
1 const maxh = 10 ;
2 maxn = 100 ; maxq = 110 ;
3 num: set of char = [ ' 0 ' .. ' 9 ' ];
4 oo = 1000000 ;
5   var c,f: array [ 0 ..maxn + 1 , 0 ..maxn + 1 ] of longint;
6 n,m,k1,k2,tx,hx,head,tail,s,t,x,y,z,i:longint;
7 pre,h: array [ 0 ..maxn + 1 ] of longint;
8 p: array [ 0 ..maxn + 1 ] of boolean;
9 q: array [ 1 ..maxq] of longint;
10   procedure getc( var x:longint);
11   var ch:char;
12   begin
13 x: = 0 ;
14 read(ch);
15 while not (ch in num) do
16 read(ch);
17 while ch in num do
18 begin
19 x: = x * 10 + ord(ch) - 48 ;
20 read(ch);
21 end ;
22 end ;
23 procedure pop;
24 begin
25 p[q[head]]: = false;
26 inc(head); inc(hx);
27 if head > maxq then head: = 1 ;
28 end ;
29 procedure push(x:longint);
30 begin
31 inc(tail); inc(tx);
32 if tail > maxq then tail: = 1 ;
33 q[tail]: = x; p[x]: = true;
34 end ;
35 function min(x,y:longint):longint;
36 begin
37 min: = x;
38 if y < x then min: = y;
39 end ;
40 begin
41 assign(input, ' PowerNet.in ' ); reset(input);
42 assign(output, ' PowerNet.out ' ); rewrite(output);
43 while not seekeof do
44 begin
45 read(n,k1,k2,m);
46 s: = n; t: = n + 1 ;
47 fillchar(c,sizeof(c), 0 );
48 for i: = 1 to m do
49 begin
50 getc(x); getc(y); getc(z);
51 c[x,y]: = c[x,y] + z;
52 end ;
53 for i: = 1 to k1 do
54 begin
55 getc(x); getc(y);
56 c[s,x]: = y;
57 end ;
58 for i: = 1 to k2 do
59 begin
60 getc(x); getc(y);
61 c[x,t]: = y;
62 end ;
63 hx: = 1 ; tx: = 0 ;
64 head: = 1 ; tail: = 0 ;
65 fillchar(p,sizeof(p),false);
66 p[s]: = true; p[t]: = true;
67 fillchar(f,sizeof(f), 0 );
68 fillchar(pre,sizeof(pre), 0 );
69 fillchar(h,sizeof(h), 0 );
70 h[s]: = maxh; dec(n);
71 for i: = 0 to n do
72 if c[s,i] > 0
73 then begin
74 h[i]: = 1 ; pre[i]: = c[s,i];
75 f[s,i]: = c[s,i]; f[i,s]: =- c[s,i];
76 push(i);
77 end ;
78 while hx <= tx do
79 begin
80 x: = q[head];
81 for i: = 0 to t do
82 begin
83 y: = c[x,i] - f[x,i];
84 if (h[x] = h[i] + 1 ) and (y > 0 )
85 then begin
86 if not p[i] then push(i);
87 z: = min(pre[x],y);
88 f[x,i]: = f[x,i] + z;
89 f[i,x]: = f[i,x] - z;
90 pre[x]: = pre[x] - z;
91 pre[i]: = pre[i] + z;
92 end ;
93 if pre[x] = 0 then break;
94 end ;
95 pop;
96 if pre[x] > 0
97 then begin
98 y: = oo;
99 for i: = 0 to t do
100 if c[x,i] > f[x,i]
101 then y: = min(y,h[i]);
102 h[x]: = y + 1 ;
103 push(x);
104 end ;
105 end ;
106 writeln(pre[t]);
107 end ;
108 close(input); close(output);
109 end .
==================================================================================

本文部分图片来源:

http://wenku.baidu.com/view/65a8290d4a7302768e99395a.html

http://wenku.baidu.com/view/6b4baf1ffc4ffe473368ab25.html

http://www.cppblog.com/mythit/archive/2009/04/19/80470.aspx

转载于:https://www.cnblogs.com/Booble/archive/2011/03/04/1970453.html

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值