题意:有n个点和m条边,给出每条边的最大容量,求从点1到点n最多可以运输多少水。
分析:
这就是典型的最大流问题。下面给出比较形式化的定义:
给定一个有向图G=(V,E),在E集合里的每条边都由三个元素来描述(u,v,c),分别表示起点、终点和容量,并指定一个源点S和一个汇点T,一个流必须满足容量限制和流量守恒。
容量限制:f(u,v)<=c(u,v),形象地理解就是流速不能超过水管的限制。
流量守恒:
形象地理解就是中转站不能把水吞掉了。
上面说的一个可行流,而所谓最大流就是要最大化:
其实就是流进汇点的流量。一般我们说一个流的流量都是指流进汇点的流量。
8.2、最大流最小割定理
割的定义:对于边集E’,若满足去掉该边集中所有边以后,源点和汇点不再连通,那么称边集E’为一个割。割的权定义为对应边集中的所有边的容量之和。最小割指的就是权最小的割。
最大流最小割定理实际上就是说:最大流的流量和最小割的权在数值上相等。
8.3、残余网络
残余网络指的其实是把所有边的权更新为w(u,v)=c(u,v)-f(u,v)。
显然初始时有
w(u,v)=c(u,v)
f(u,v)=0
形象地看,就是说这个水管还能容纳多少流量。一般地,我们只需要注意残余网络就可以求出最大流。为了方便下面都用w(u,v)来表示(u,v)这条边的残余流量。
为了实现下面的增广路算法,这里引入反向弧的概念。所谓反向弧,其实是用以修正前面错误的流的一种修正边。对于原图的每条有向边,我们都给它配对一条反向弧,用以修正该条有向边上错误的流。
假设给定的有向边是(u,v),那么给它配对的反向弧就是一条(v,u)这样反向的边并且满足以下这个性质:
w(v,u)=f(u,v)
所以有推论w(v,u)+w(u,v)=c(u,v)
因为流的正反向抵消性质(水从u流到v,再从v流回u,其实就是什么都没发生):
1、如果通过反向弧(v,u)的残余流量增加了流量t,其实相当于f(u,v)=f(u,v)-t,按照上面的性质,其实就是
w(v,u)=w(v,u)-t
w(u,v)=w(u,v)+t
2、如果通过正向弧(v,u)的残余流量增加了流量t,其实相当于f(u,v)=f(u,v)+t,按照上面的性质,其实就是
w(v,u)=w(v,u)+t
w(u,v)=w(u,v)-t
发现了吗?它们是轮换对称的。所以在实际实现中并不需要区分哪条是正向边,哪条是反向弧,假如利用当前边增加了流量t,那么就将当前边的残余流量减去t,再将对应的另一条加上t就可以了。为了方便,我们称正向边和反向弧互为反边,并用op(e)表示e这条边的反边。
8.4、基于最大流最小割定理求最大流的算法(统称Ford-Fulkerson算法)
Ford-Fulkerson是求最大流很经典的算法。该算法就是不断在残余网络中寻找增广路并增广,直到找不到增广路为止(也就是说,此时源点和汇点不连通,存在割)。下面给出增广路和增广的含义。
增广路:一条从起始于源点S,终止于汇点T的路径。且必须满足路径上的每条边的残余流量都大于0。
增广:增广是对于一条增广路来说的。令拼接成增广路的有向边组成边集E’。再令,然后对于,进行下面操作:
w(e)=w(e)-delta
w(op(e))=w(op(e))+delta
然后流进汇点T的流就加上delta
代码:
{ ID: ymwbegi1 PROG: ditch LANG: PASCAL } var n,m,ans:longint; c:array[1..200,1..200] of longint; fa:array[1..200] of longint; procedure init; var i,x,y,z:longint; begin readln(m,n); for i:=1 to m do begin readln(x,y,z); inc(c[x,y],z); end; end; function find(x:longint):boolean; var i:longint; begin if x=n then exit(true); for i:=1 to n do if (fa[i]=-1)and(c[x,i]>0) then begin fa[i]:=x; if find(i) then exit(true); end; find:=false; end; procedure add; var i,min:longint; begin min:=maxlongint; i:=n; while i>1 do begin if c[fa[i],i]<min then min:=c[fa[i],i]; i:=fa[i]; end; i:=n; ans:=ans+min; while i>1 do begin dec(c[fa[i],i],min); inc(c[i,fa[i]],min); i:=fa[i]; end; end; procedure main; var i:longint; begin for i:=2 to n do fa[i]:=-1; fa[1]:=0; while find(1) do begin add; for i:=2 to n do fa[i]:=-1; fa[1]:=0; end; end; begin assign(input,'ditch.in'); assign(output,'ditch.out'); reset(input); rewrite(output); init; main; writeln(ans); close(input); close(output); end.