网络流的定义
给定一个有向图G=(V,E),把图中的边看作管道,每条边上有一个权值,表示该管道的流量上限。给定源点s和汇点t,现在假设在s处有一个水源,t处有一个蓄水池,问从s到t的最大水流量是多少。
为了方便作图,这里用
来表示一条从A到B的路径。
比如这样一个图,最大流为1(1-2-4-6)+3(1-3-4-6)+2(1-3-5-6)=6
如何求最大流,就是网络流要解决的问题。
概念
如果只是用普通的DFS或BFS的话,有可能会通过1-2-3-4得出最大流为100,但实际上可以通过1-2-4和1-3-4得到最大流为200。
所以为了保证结果正确,我们定义一个反向边。
每次找到一条到汇点的路径时,把路径上的每条边都减去得到的值,再在每条路径上的边的两个点中建立一条反向边,权值即为算出的结果。
例如上面这个图,从1-2-3-4走过后即为这样一个图:
为什么保证这样做是正确的,因为根据图可以看出,此时的图中多出了一条1-3-2-4的边,结果为100。
这样一走,相当于中间那条边没被动过,只走了上下两条路径。
此时发现不能走了,结果即为100+100=200。
残余网络(Residual Network)
在一个网络流图上,找到一条源到汇的路径(即找到了一个流量)后,对路径上所有的 边,其容量都减去此次找到的流量,对路径上所有的边,都添加一条反向边,其容量也 等于此次找到的流量,这样得到的新图,就称为原图的“残余网络”。
时间复杂度
找增广路径的算法可以用dfs, 复杂度为边数m+顶点数n Dfs最多运行C次,所以时间复杂度为C*(m+n) =C* n^2
但是C有可能会很大,比如这样:
如果RP不好,有可能会在中间2-3中不停的走,但实际上只要1-2-4和1-3-4就可以得出结果。
所以把这个暴力算法改进一下,就变成了Dinic算法。
正式的讲解
在暴力的基础上,每求出来一次残余网络时就把当前的图的点分层。
每一个点的层数即为源点到该点经过最少边数。
每一个点每一次走都必须走到下一层的点。
例如第0层的点只能走到第1层,第1层只能走到第2层。
这样做就有效避免了刚刚出现的情况:
经过分层之后,点2无法走到点3,因为它们处于同一层。
所以点2不会走到点3,只会走1-2-4。
接下来如果从3走到2,会发现没有路走到4,无法走到层数比它小的1。
所以可以快速算出答案。
关于返回的问题
因为有可能经过一次计算之后,某些路径的值已经变为0。如果继续计算,不但没有意义,还会浪费时间。
所以在找到一条路径值为0时,把该条路径的层数记录下来,之后如果当前枚举的点的层数大于记录的点时,可以直接退出。
Dinic流程
while (BFS分层—>DFS求路径)
直到BFS无法算出汇点层数
(即已经没有路径可以到达汇点)
关于反向边的处理
可以设一个数组S,S[i,j]表示从点i到点j的边积累的权值。
每次找到一条边,就把当前这条边加上对应的S[i,j]。
如果要给i-j这样一条边建立反向边,可以通过更改S[j,i]来实现。
因为等到枚举到j-i时,会自动加上S[j,i]。
所以刚开始先把每一条边都建一条反向边,权值设为0,就不用以后再去建新边。
这样做虽然会有些延迟,但是总比直接加方便了很多,还不会影响到正确性。
代码
const
len=10000;
var
xx:array[1..200,1..200] of longint;
//记录初始图
a:array[1..401,1..4] of longint;
//处理边
s:array[1..200,1..200] of longint;
//处理反向边
b:array[1..200] of longint;
c:array[0..200] of longint;
f:array[1..200] of longint;
p:array[1..200] of longint;
//栈,记录路径边编号
n,m,i,j,k,l,x,y,z,ans,ss:longint;
function min(x,y:longint):longint;
begin
if x<y then min:=x
else
min:=y;
end;
procedure swap(var x,y:longint);
var
z:longint;
begin
z:=x;
x:=y;
y:=z;
end;
procedure qsort(l,r:longint);
var
i,j,mid:longint;
begin
i:=l;
j:=r;
mid:=a[(l+r) div 2,1];
while i<=j do
begin
while (a[i,1]<mid) do inc(i);
while (a[j,1]>mid) do dec(j);
if i<=j then
begin
swap(a[i,1],a[j,1]);
swap(a[i,2],a[j,2]);
swap(a[i,3],a[j,3]);
inc(i);
dec(j);
end;
end;
if l<j then qsort(l,j);
if i<r then qsort(i,r);
end;
procedure bfs;
var
d:array[1..len] of longint;
i,j,k,h,t:longint;
begin
fillchar(f,sizeof(f),10);
d[1]:=1;
f[1]:=0;
h:=0;
t:=1;
while h<>t do
begin
inc(h);
if h>len then
h:=1;
j:=d[h];
for i:=c[j] to c[j+1]-1 do
if (a[i,3]+s[a[i,1],a[i,2]]>0) and (f[j]+1<f[a[i,2]]) then
//如果有路径可以往下走
begin
k:=a[i,2];
inc(t);
if t>len then
t:=1;
d[t]:=k;
f[k]:=f[j]+1;
end;
end;
if f[m]>1000000 then
//如果无法到达汇点,则输出答案
begin
writeln(ans);
halt;
end;
end;
procedure dfs(t,l:longint);
var
i,j,sum:longint;
begin
if t=m then
begin
sum:=maxlongint;
for i:=1 to l-1 do
sum:=min(sum,a[p[i],3]);
//计算流量值
ans:=ans+sum;
ss:=l;
for i:=1 to l-1 do
begin
a[p[i],3]:=a[p[i],3]-sum;
inc(s[a[p[i],2],a[p[i],1]],sum);
//添加反向边
if (ss=l) and (a[p[i],3]=0) then
//寻找边权为0的边
ss:=i;
end;
bfs;
exit;
end;
for i:=c[t] to c[t+1]-1 do
if (f[t]+1=f[a[i,2]]) and (a[i,3]+s[a[i,1],a[i,2]]>0) then
begin
a[i,3]:=a[i,3]+s[a[i,1],a[i,2]];
//将有可能的反向边加入
s[a[i,1],a[i,2]]:=0;
p[l]:=i;
dfs(a[i,2],l+1);
if l>ss then
//如果层数比记录边的层数大就退出
exit
else
ss:=maxlongint;
//否则停止弹栈
end;
end;
begin
readln(n,m);
for i:=1 to n do
begin
readln(x,y,z);
xx[x,y]:=xx[x,y]+z;
end;
n:=0;
for i:=1 to m do
for j:=1 to m do
if xx[i,j]>0 then
begin
inc(n);
a[n,1]:=i;
a[n,2]:=j;
a[n,3]:=xx[i,j];
inc(b[i]);
end;
for i:=1 to n do
if xx[a[i,2],a[i,1]]=0 then//先建好权值为0的反向边
begin
inc(n);
inc(b[a[i,2]]);
a[n,1]:=a[i,2];
a[n,2]:=a[i,1];
a[n,3]:=0;
end;
c[1]:=1;
for i:=2 to m+1 do
c[i]:=c[i-1]+b[i-1];
qsort(1,n);
ss:=maxlongint;
while 1=1 do
begin
bfs;
dfs(1,1);
end;
end.