最大流之Dinic 算法

 看了http://www.cnblogs.com/SYCstudio/p/7260613.html

 https://comzyh.com/blog/archives/568/两位大佬的博客 颇有收获 小小总结一下

一、Dinic算法的基本思路:

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

Drainage Ditches

二、几个概念 

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

  • 残量网络:包含反向弧的有向图,Dinic要循环的,每次修改过的图都是残量网络,
  • 层次图:分层图,以[从原点到某点的最短距离]分层的图,距离相等的为一层,(比如上图的分层为{1},{2,4},{3})
  • DFS:这个就不用说了吧…
  • 增广  :在现有流量基础上发现新的路径,扩大发现的最大流量(注意:增加量不一定是这条路径的流量,而是新的流量与上次流量之差)
  • 增广路:在现有流量基础上发现的新路径.(快来找茬,和上一条有何不同?)
  • 剩余流量:当一条边被增广之后(即它是增广路的一部分,或者说增广路通过这条边),这条边还能通过的流量.
  • 反向弧:我们在Dinic算法中,对于一条有向边,我们需要建立另一条反向边(弧),当正向(输入数据)边剩余流量减少I时,反向弧剩余流量增加I
  • 注意:Dinic算法引入了一个叫做分层图的概念。具体就是对于每一个点,我们根据从源点开始的bfs序列,为每一个点分配一个深度,然后我们进行若干遍dfs寻找增广路,每一次由u推出v必须保证v的深度必须是u的深度+1。

 三、流程

  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->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流做加法.

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

算法模板 

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#define min(x,y) ((x<y)?(x):(y))
using namespace std;
const int MAX=0x5fffffff;
int tab[250][250];//邻接矩阵
int dis[250];//距源点距离,分层图
int q[2000],h,r;//BFS队列 ,首,尾
int N,M,ANS;//N:点数;M,边数
int BFS()
{
     int i,j;
     memset(dis,0xff,sizeof(dis));//以-1填充
     dis[1]=0;
     h=0;r=1;
     q[1]=1;
     while (h<r)
     {
           j=q[++h];
           for (i=1;i<=N;i++)
               if (dis[i]<0 && tab[j][i]>0)
               {
                  dis[i]=dis[j]+1;
                  q[++r]=i;
               }
     }
     if (dis[N]>0)
        return 1;
     else
        return 0;//汇点的DIS小于零,表明BFS不到汇点
}
//Find代表一次增广,函数返回本次增广的流量,返回0表示无法增广
int find(int x,int low)//Low是源点到现在最窄的(剩余流量最小)的边的剩余流量
{
    int i,a=0;
    if (x==N)return low;//是汇点
    for (i=1;i<=N;i++)
    if (tab[x][i] >0 //联通
     && dis[i]==dis[x]+1 //是分层图的下一层
     &&(a=find(i,min(low,tab[x][i]))))//能到汇点(a <> 0)
    {
       tab[x][i]-=a;
       tab[i][x]+=a;
       return a;
    }
    return 0;

}
int main()
{
    int i,j,f,t,flow,tans;
    while (scanf("%d%d",&M,&N)!=EOF){
    for (i=1;i<=M;i++)    memset(tab,0,sizeof(tab));

    {
        scanf("%d%d%d",&f,&t,&flow);
        tab[f][t]+=flow;
    }
    //
    ANS=0;
    while (BFS())//要不停地建立分层图,如果BFS不到汇点才结束
    {
          while(tans=find(1,0x7fffffff))ANS+=tans;//一次BFS要不停地找增广路,直到找不到为止
    }
    printf("%d\n",ANS);
    }
    system("pause");
}

猜你喜欢

转载自blog.csdn.net/Destinymiao/article/details/81407393