网络流Dinic(【USACO题库】4.2.1 Drainage Ditches草地排水 )

网络流-Sap在此

这里写图片描述

网络流的定义

给定一个有向图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.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值