网络流征程——dinic算法

首先,大家都说dinic好,我就毫不犹豫地先学习一下。

自我认为v^2*e<e^2*v(SAP)。

首先,几个基础知识的tips:

一般情况下在Dinic算法中,我们只记录某一边的剩余流量.

  • 残量网络:包含反向弧的有向图,Dinic要循环的,每次修改过的图都是残量网络,
  • 层次图:分层图,以[从原点到某点的最短距离]分层的图,距离相等的为一层,(比如上图的分层为{1},{2,4},{3})
  • DFS:这个就不用说了吧…
  • 增广  :在现有流量基础上发现新的路径,扩大发现的最大流量(注意:增加量不一定是这条路径的流量,而是新的流量与上次流量之差)
  • 增广路:在现有流量基础上发现的新路径.(快来找茬,和上一条有何不同?)
  • 剩余流量:当一条边被增广之后(即它是增广路的一部分,或者说增广路通过这条边),这条边还能通过的流量.
  • 反向弧:我们在Dinic算法中,对于一条有向边,我们需要建立另一条反向边(弧),当正向(输入数据)边剩余流量减少I时,反向弧剩余流量增加I
第二,引自nocow的解释:

Dinic算法的基本思路:
  1. 根据残量网络计算层次图。
  2. 在层次图中使用DFS进行增广直到不存在增广路
  3. 重复以上步骤直到无法增广
然后,特别将 反向弧 疑难点领出来给大家一句话概括:

假设以后你发现这条增广路并不是最优的,反向弧给你一个反悔的机会,让你可以用一条更优的替换掉原来的。

举一个例子:

在这幅图中我们首先要增广1->2->4->6,这时可以获得一个容量为2的流,但是如果不建立4->2反向弧的话,则无法进一步增广,最终答案为2,显然是不对的,然而如果建立了反向弧4->2,则第二次能进行1->3->4->2->5->6的增广,最大流为3.

必须使用反向弧的流网络

Comzyh对反向弧的理解可以说是”偷梁换柱“,请仔细阅读:在上面的例子中,我们可以看出,最终结果是1->2->5->6和1->2->4->6和1->3->4->6.当增广完1->2->4->5(代号A)后,在增广1->3->4->2->5->6(代号B),相当于将经过节点2的A流从中截流1(总共是2)走2->5>6,而不走2->4>6了,同时B流也从节点4截流出1(总共是1)走4->6而不是4->2->5->6,相当于AB流做加法.

程序实现:

type
  node=record
        tt,V,next:longint;
       end;
const
  INF=maxlongint shr 2;
var
  cnt,x,y,z,start,stp,n,m,maxflow,i:longint;
  level,a,q:array[0..100000]of longint;
  e:array[0..200000]of node;
function min(a,b:longint):longint;
begin
  if a<b then exit(a);
  exit(b);
end;
procedure newnode(x,y,z:longint);
begin
  e[cnt].tt:=y;
  e[cnt].V:=z;
  e[cnt].next:=a[x];
  a[x]:=cnt;
  cnt:=cnt+1;
end;
procedure init;
var
  i,x,y,z:longint;
begin
  readln(n,m);
  for i:=1 to m do
   begin
     readln(x,y,z);
     newnode(x,y,z);
     newnode(y,x,0);
   end;
  readln(start,stp);
end;
function bfs:boolean;
var
  head,tail,i,j:longint;
begin
  fillchar(level,sizeof(level),$ff);
  head:=0;tail:=1;
  q[1]:=start;level[start]:=0;
  while head<tail do
   begin
     head:=head+1;
     i:=q[head];
     j:=a[i];
     while j>0 do
      begin
        if (e[j].V>0)and(level[e[j].tt]=-1) then
         begin
           tail:=tail+1;
           level[e[j].tt]:=level[i]+1;
           q[tail]:=e[j].tt;
         end;
        j:=e[j].next;
      end;
   end;
  exit(level[stp]>-1);
end;//目的:保证增广路长度最短,加快增广判断速度,避免走回头路
function dfs(x,f:longint):longint;//f为当前剩余流量,x为结点号
var
  w,used,i:longint;
begin
  if x=stp then exit(f);
  used:=0;i:=a[x];//used表示这一层共用的流量数
  while i>0 do
   begin
     if (e[i].v>0)and(level[e[i].tt]=level[x]+1) then
      begin
        w:=f-used;
        w:=dfs(e[i].tt,min(e[i].v,w));//当前边和剩余流量取小值即为可流量
        e[i].v:=e[i].v-w;
        used:=used+w;
        e[i xor 1].v:=e[i xor 1].v+w;//反向弧处理
        if used=f then exit(f);//流量已用完
      end;
     i:=e[i].next;
   end;
  if used=0 then level[i]:=-1;//没有增广路
  exit(used);
end;
procedure solve;
var
  maxflow:longint;
begin
  maxflow:=0;
  while bfs do inc(maxflow,dfs(start,INF));
  write(maxflow);
end;
procedure openfile;
begin
  assign(input,'dinic.in');reset(input);
  assign(output,'dinic.out');rewrite(output);
end;
procedure closfile;
begin
  close(input);close(output);
end;
procedure main;
begin
  //openfile;
  init;
  solve;
  closfile;
end;
begin
  main;
end.

//本文部分摘自comzyh的博客

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值