Dinic算法詳解

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

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

網絡流博大精深—sideman

一個基本的網絡流問題


感謝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

·         DinicDFS時只在分層圖中DFS,意思是說DFS的下一個節點的Dis(距源點的距離)要比自己的Dis1,例如在圖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->61->2->4->6 1->3->4->6.當增廣完1->2->4->6(代號A),在增廣 1->3->4->2->5->6(代號B),相當於將經過節點2A流從中截流1(總共是2)2->5>6,而不走2->4>6,同時B流也從節點4截流出1(總共是1)4->6而不是4->2->5->6,相當於AB流做加法.

 

簡單的說反向弧爲今後提供反悔的機會,讓前面不走這條路而走別的路.

最大流算法一直有一個入門經典題:POJ 1273 或者是UCACO 4_2_1 來自NOCOW(中文) 這兩個是同一個題

給出這道題的代碼

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
#include<queue>
#include<vector>
#include<string>
#define Min(a,b) a<b?a:b
#define Max(a,b) a>b?a:b
#define CL(a,num) memset(a,num,sizeof(a));
#define eps  1e-12
#define inf   0x7fffffff

//freopen("data.txt","r",stdin);
const double pi  = acos(-1.0);
typedef   __int64  ll;
const int maxn = 300 ;
using namespace std;
int n , m;
int flow[maxn][maxn],dis[maxn] ;//dis[i],表示  到 原點  s 的 層數 
  
int bfs()// 重新 建 圖 (按 層數 建圖)
{
    CL(dis,-1);
    dis[1] = 0 ;
    queue<int>que;
    que.push(1);
    while(!que.empty())
    {
        int  k = que.front();que.pop() ;
        for( int i = 1;i<= n;i++)
        {
            if(flow[k][i] > 0 && dis[i] < 0 )// 如果 可以  可以到達 但 還沒有 訪問
            {
                dis[i] = dis[k]+ 1 ;
                que.push(i) ;
            }
        }
    }

    if(dis[n] > 0) return 1;
    else return  0 ;

}
int  dfs(int x,int mx)// 查找  路徑上的 最小 的 流量
{

    int i , a ;
    if(x == n) return mx ;

    for(i = 1;i<= n;i++)
    {
        if(flow[x][i] > 0 && dis[i] == dis[x] + 1  && (a =dfs(i,min(mx,flow[x][i]))))
        {
            flow[x][i] -= a;
            flow[i][x] += a;
            return a ;


        }
    }
    return 0 ;
}
int main()
{
    //freopen("data.txt","r",stdin);
    int i ,s,e,c;
    while(scanf("%d%d",&m,&n)!=EOF)
    {
        CL(flow,0);
        for(i = 0  ; i < m;i++)
        {
            scanf("%d%d%d",&s,&e,&c);
            flow[s][e] += c;
        }
      int ans = 0;
      int res;

      while(bfs())
      {


         while(res = dfs(1,inf)) ans+= res ;

      }
      printf("%d\n",ans);
    }

}


更高效的 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() ;
          for( int 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] > 0) return 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);
     }
  }


 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章