dinic (最大流) 算法 讲解

 

网络流入门—用于最大流的Dinic算法

转自:http://comzyh.tk/blog/archives/568/

“网络流博大精深”—sideman语

Drainage Ditches

一个基本的网络流问题


感谢WHD的大力支持

 

最早知道网络流的内容便是最大流问题,最大流问题很好理解:

解释一定要通俗!

如右图所示,有一个管道系统,节点{1,2,3,4},有向管道{A,B,C,D,E},即有向图一张. [1]是源点,有无限的水量,[4]是汇点,管道容量如图所示.试问[4]点最大可接收的水的流量?

这便是简单的最大流问题,显然[4]点的最大流量为50

死理性派请注意:流量是单位时间内的,总可以了吧!

然而对于复杂图的最大流方法是什么呢,有EK,Dinic,SAP,etc.下面介绍Dinic算法(看代码的直接点这)

Dinic 算法

Dinic算法的基本思路:
  1. 根据残量网络计算层次图。
  2. 在层次图中使用DFS进行增广直到不存在增广路
  3. 重复以上步骤直到无法增广

引自NOCOW,相当简单是吧...

小贴士:

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

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


Dinic动画演示

  1. 用BFS建立分层图  注意:分层图是以当前图为基础建立的,所以要重复建立分层图
  2. 用DFS的方法寻找一条由源点到汇点的路径,获得这条路径的流量I 根据这条路径修改整个图,将所经之处正向边流量减少I,反向边流量增加I,注意I是非负数
  3. 重复步骤2,直到DFS找不到新的路径时,重复步骤1

注意(可以无视):

  • Dinic(其实其他的好多)算法中寻找到增广路后要将反向边增加I
  • Dinic中DFS时只在分层图中DFS,意思是说DFS的下一个节点的Dis(距源点的距离)要比自己的Dis大1,例如在图1中第一个次DFS中,1->2->4 这条路径是不合法的,因为Dis[2]=1;Dis[4]=1;
  • 步骤2中"获得这条路径的流量I "实现:DFS函数有参量low,代表从源点到现在最窄的(剩余流量最小)的边的剩余流量,当DFS到汇点是,Low便是我们要的流量I
对于反向弧(反向边)的理解:

这一段不理解也不是不可以,对于会写算法没什么帮助,如果你着急,直接无视即可.
先举一个例子(如右图):

必须使用反向弧的流网络

 

必须使用反向弧的流网络

在这幅图中我们首先要增广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->6(代号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流做加法.

 

简单的说反向弧为今后提供反悔的机会,让前面不走这条路而走别的路.

Dinic算法的程序实现

最大流算法一直有一个入门经典题:POJ 1273 或者是UCACO 4_2_1 来自NOCOW(中文) 这两个是同一个题

给出这道题的代码

 

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<cmath>
 4 #include<iostream>
 5 #include<algorithm>
 6 #include< set>
 7 #include<map>
 8 #include<queue>
 9 #include<vector>
10 #include< string>
11  #define Min(a,b) a<b?a:b
12  #define Max(a,b) a>b?a:b
13  #define CL(a,num) memset(a,num,sizeof(a));
14  #define eps  1e-12
15  #define inf   0x7fffffff
16 
17  // freopen("data.txt","r",stdin);
18  const  double pi  = acos(- 1.0);
19 typedef   __int64  ll;
20  const  int maxn =  300 ;
21  using  namespace std;
22  int n , m;
23  int flow[maxn][maxn],dis[maxn] ; // dis[i],表示  到 原点  s 的 层数 
24    
25  int bfs() //  重新 建 图 (按 层数 建图)
26  {
27     CL(dis,- 1);
28     dis[ 1] =  0 ;
29     queue< int>que;
30     que.push( 1);
31      while(!que.empty())
32     {
33          int  k = que.front();que.pop() ;
34          forint i =  1;i<= n;i++)
35         {
36              if(flow[k][i] >  0 && dis[i] <  0 ) //  如果 可以  可以到达 但 还没有 访问
37              {
38                 dis[i] = dis[k]+  1 ;
39                 que.push(i) ;
40             }
41         }
42     }
43 
44      if(dis[n] >  0return  1;
45      else  return   0 ;
46 
47 }
48  int  dfs( int x, int mx) //  查找  路径上的 最小 的 流量
49  {
50 
51      int i , a ;
52      if(x == n)  return mx ;
53 
54      for(i =  1;i<= n;i++)
55     {
56          if(flow[x][i] >  0 && dis[i] == dis[x] +  1  && (a =dfs(i,min(mx,flow[x][i]))))
57         {
58             flow[x][i] -= a;
59             flow[i][x] += a;
60              return a ;
61 
62 
63         }
64     }
65      return  0 ;
66 }
67  int main()
68 {
69      // freopen("data.txt","r",stdin);
70       int i ,s,e,c;
71      while(scanf( " %d%d ",&m,&n)!=EOF)
72     {
73         CL(flow, 0);
74          for(i =  0  ; i < m;i++)
75         {
76             scanf( " %d%d%d ",&s,&e,&c);
77             flow[s][e] += c;
78         }
79        int ans =  0;
80        int res;
81 
82        while(bfs())
83       {
84 
85 
86           while(res = dfs( 1,inf)) ans+= res ;
87 
88       }
89       printf( " %d\n ",ans);
90     }
91 
92 }

 更高效的 dinic

 

hdu 4292   Food 

 

 

 

#include<cstdio>
  #include<cstring>
  #include<cmath>
  #include<iostream>
  #include<algorithm>
  #include< set>
  #include<map>
  #include<queue>
  #include<vector>
  #include< string>
  #define INF 0x3fffffff
  #define F(x) (x)
  #define N(x) (205+(x))
  #define CPN(x) (410+(x))
  #define D(x) (615+(x))
  #define maxn 250
  #define CL(a,b) memset(a,b,sizeof(a))
  #define Pnum 210
  using  namespace std;
 
   int next[maxn* 20],dis[maxn* 10];
   int s,e;
   int  n, fnum ,dnum ,f[maxn],d[maxn],cnt;
   struct node
  {
       int to;
       int cap ;
       int next ;
  }p[ 200000] ;
    int que[maxn*maxn] ,idx;
 
   void add( int x, int y, int cap)// 建边 注意 反向边的流量为 0
  {
      p[cnt].to = y;
      p[cnt].cap = cap ;
      p[cnt].next = next[x];
      next[x] = cnt++ ;
 
      p[cnt].to = x;
      p[cnt].cap =  0;
      p[cnt].next = next[y];
      next[y] = cnt++ ;
  }
   int bfs() //  重新 建 图 (按 层数 建图)
  {
 
      memset(dis, 0xff, sizeof(dis)) ;
      dis[s] =  0 ;
      queue< int>que;
      que.push(s);
 
       while(!que.empty())
      {
 
           int  k = que.front();que.pop() ;
           forint i = next[k];i!=- 1;i = p[i].next)
          {
               int v = p[i].to;
 
 
               int cap = p[i].cap ;
 
               if(cap >  0 && dis[v] <  0 ) //  如果 可以  可以到达 但 还没有 访问
              {
 
                  dis[v] = dis[k]+  1 ;
                  que.push(v) ;
              }
          }
 
      }
 
 
       if(dis[e] >  0return  1;
       else  return   0 ;
 
  }
 
  
 
   int  dfs( int x, int mx) //  查找  路径上的 最小 的 流量
  {
 
       int i , a ,tf =   0;
 
       if(x == e)  return mx ;
 
       for(i = next[x];i!= -  1;i = p[i].next)
      {
           int v = p[i].to ;
           int cap = p[i].cap ;
           if(cap >  0 && dis[v] == dis[x] +  1  && (a =dfs(v,min(cap,mx))))
          {
 
              p[i].cap -= a;
              p[i^ 1].cap += a;
 
                 return a;
 
 
          }
      }
       if(!tf) dis[x] = - 1; //  没有 找到 最小流量 ,说明 从这个点到不了 终点 ,所以  标记一下
       return tf ;
  }
 
 
 
 
  
   int main()
  {
      int i , j ;
      char c[ 250] ;
       // freopen("data.txt","r",stdin) ;
      while(scanf( " %d%d%d ",&n,&fnum,&dnum)!=EOF)
     {
 
         CL(next,- 1) ;
         cnt =  0;
          s =  0;
          e =  2000;
           for(i =  1 ; i <= fnum;i++)
          {
              scanf( " %d ",&f[i]);
          }
           for(i =  1 ; i<= dnum;i++)
          {
              scanf( " %d ",&d[i]) ;
          }
           for(i =  1; i <= n;i++) //   人 和 吃的
          {
              scanf( " %s ",c);
               for(j =  0 ;  j< fnum ;j++)
              {
                   if(c[j] ==  ' Y ')
                  {
 
                      add(j +  1,i + Pnum, 1) ;
                  }
              }
 
          }
           for(i =  1; i<= n;i++) //   人 和 喝的
          {
              scanf( " %s ",c);
               for(j =  0 ;  j< dnum ;j++)
              {
                   if(c[j] ==  ' Y ')
                  {
 
                      add(i + Pnum* 2,j + Pnum* 3 +  1, 1) ;
                  }
              }
 
          }
           for(i =  1; i <= fnum;i++) // 增加源点
          {
 
              add( 0,i,f[i]) ;
          }
           for(i = Pnum* 3  +  1,j =  1; j <= dnum;i++,j++) // 增加 汇点
          {
 
              add(i,e,d[j]) ;
 
          }
 
           for(i =  1; i <= n;i++) //   将人 拆分
          {
 
              add(i + Pnum,i +Pnum* 2, 1);
          }
             int ans =  0;
         int res;
 
         while(bfs())
        {
 
 
            while(res = dfs(s,INF)) ans+= res ;
 
        }
        printf( " %d\n ",ans);
     }
  }

 

转载于:https://www.cnblogs.com/acSzz/archive/2012/09/13/2683820.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值