zoukankan      html  css  js  c++  java
  • 网络流之最大流算法

    首先是网络流中的一些定义:

    V表示整个图中的所有结点的集合.
    E表示整个图中所有边的集合.
    G = (V,E) ,表示整个图.
    s表示网络的源点,t表示网络的汇点.
    对于每条边(u,v),有一个容量c(u,v)   (c(u,v)>=0),如果c(u,v)=0,则表示(u,v)不存在在网络中。相反,如果原网络中不存在边(u,v),则令c(u,v)=0.
    对于每条边(u,v),有一个流量f(u,v).

    一个简单的例子.网络可以被想象成一些输水的管道.括号内右边的数字表示管道的容量c,左边的数字表示这条管道的当前流量f.

    网络流的三个性质:

    1、容量限制:  f[u,v]<=c[u,v]
    2、反对称性:f[u,v] = - f[v,u]
    3、流量平衡:  对于不是源点也不是汇点的任意结点,流入该结点的流量和等于流出该结点的流量和。
    只要满足这三个性质,就是一个合法的网络流.

    最大流问题,就是求在满足网络流性质的情况下,源点 s 到汇点 t 的最大流量。

    我学的是dinic的算法来做最大流。

    概述:Dinic算法的思路是这样的:每次都不停地用BFS来构造“层次图”,然后用“阻塞流”来增广

    什么是层次图呢?假设在残余网络中,起点到结点u的距离是dist(u),那么dist(u)就是结点u的“层次”。只保留每个点出发到下一层次的弧,就得到了一张层次图。

    什么是阻塞流呢?其实就是不考虑反向弧时的“极大流”,比如从s到t的层次图(注意此时是残量网络)中找到了一条简单路径是s->1->3->t。其中s->1的容量是10,1->3的容量是4,3->t的容量是10。那么极大流就是4(因为最大不能超过路径上所有容量的最小值)。4也就是阻塞流。可以直接理解为简单路径上的最小残量值。

     算法步骤

    1、初始化流量,计算出剩余图

    2、根据剩余图计算层次图。若汇点不在层次图内,则算法结束

    3、在层次图内不断用bfs增广,直到层次图内没有增广路为止

    4、转步骤2

    Dinic算法复杂度分析

    Dinic算法最多被分为n个阶段,每个阶段包括建层次网络和寻找增广路两部分,其中建立层次网络的复杂度仍是O(n*m)。

    现在来分析DFS过程的总复杂度。在每一阶段,将DFS分成两部分分析。

    (1)修改增广路的流量并后退的花费。在每一阶段,最多增广m次,每次修改流量的费用为O(n)。而一次增广后在增广路中后退的费用也为O(n)。所以在每一阶段中,修改增广路以及后退的复杂度为O(m*n)。

    (2)DFS遍历时的前进与后退。在DFS遍历时,如果当前路径的最后一个顶点能够继续扩展,则一定是沿着第i层的顶点指向第i+1层顶点的边向汇点前进了一步。因为增广路经长度最长为n,所以最坏的情况下前进n步就会遇到汇点。在前进的过程中,可能会遇到没有边能够沿着继续前进的情况,这时将路径中的最后一个点在层次图中删除。

    注意到每后退一次必定会删除一个顶点,所以后退的次数最多为n次。在每一阶段中,后退的复杂度为O(n)

    假设在最坏的情况下,所有的点最后均被退了回来,一共共后退了n次,这也就意味着,有n次的前进被“无情”地退了回来,这n次前进操作都没有起到“寻找增广路”的作用。除去这n次前进和n次后退,其余的前进都对最后找到增广路做了贡献。增广路最多找到m次。每次最多前进n个点。所以所有前进操作最多为n+m*n次,复杂度为O(n*m)。

    于是得到,在每一阶段中,DFS遍历时前进与后退的花费为O(m*n)。

    综合以上两点,一次DFS的复杂度为O(n*m)。因此,Dinic算法的总复杂度即O(m*n*n)。

    优化:  当前弧优化,是什么意思呢?注意在DFS中用cur[x]表示当前应该从x的编号为cur[x]的边开始访问,也就是说从0到cur[x]-1的这些边都不用再访问了,相当于删掉了,达到了满流。DFS(x,a)表示当前在x节点,有流量a,到终点t的最大流。当前弧优化在DFS里的关键点在if(MM==0) break;也就是说对于结点x,如果x连接的前面一些弧已经能把a这么多的流量都送到终点,就不需要再去访问后面的一些弧了,当前未满的弧和后面未访问的弧等到下次再访问结点x的时候再去增广。

    但实际上Dinic算法比这个理论上界好得多。如果所有边容量均为1,那么时间复杂度是O(min(N^0.67,M^0.5)*M);对于二分图最大匹配这样的特殊图,时间复杂度是O(N^0.5*M)。


    下面是用网络流实现的完美的牛栏,(⊙o⊙)…。

      1 #include<cstdio>
      2 #include<algorithm>
      3 #include<cmath>
      4 #include<iostream>
      5 #include<cstring>
      6 using namespace std;
      7 
      8 const int INF=1e8+8;
      9 
     10 int n,m,S,T;
     11 int cnt,head[507],next[41007],val[41007],rea[41007];
     12 int dis[507],p[507];//因为是宽搜,所以p的空间开节点个数 
     13 
     14 void add(int u,int v,int zhi)
     15 {
     16     cnt++;
     17     next[cnt]=head[u];
     18     head[u]=cnt;
     19     rea[cnt]=v;
     20     val[cnt]=zhi;
     21 }
     22 bool bfs()
     23 {
     24     memset(dis,-1,sizeof(dis));
     25     dis[S]=0;
     26     int hd=0,tl=1;
     27     p[tl]=S;
     28     while (hd<tl)
     29     {
     30         hd++;
     31         int u=p[hd];
     32         for (int i=head[u];i!=-1;i=next[i])
     33         {
     34             int v=rea[i],cost=val[i];
     35             if (dis[v]==-1&&cost>0)//如果当前阻塞流的流量为0,则不必要到达 
     36             {
     37                 dis[v]=dis[u]+1;
     38                 if (v==T) return 1;//到达终点即可 
     39                 p[++tl]=v;
     40             }
     41         }
     42     }
     43     return 0;
     44 }
     45 int dfs(int u,int MM)
     46 {
     47     if (!MM||u==T) return MM;
     48     int res=0;
     49     for (int i=head[u];i!=-1;i=next[i])
     50     {
     51         int v=rea[i],cost=val[i];
     52         if (dis[v]!=dis[u]+1) continue;//严格按照层次来进行 
     53         int x=dfs(v,min(MM,cost));
     54         if (x)
     55         {
     56             val[i]-=x,val[i^1]+=x;
     57             MM-=x,res+=x;
     58             if (!MM) break;//当前弧优化 
     59         }
     60     }
     61     if (MM==-1) dis[u]=-1;
     62     return res;
     63 }
     64 int dinic()
     65 {
     66     int res=0;
     67     while (bfs())//构造层次图,一旦无法到达终点,则增广结束,已经求得了最大流 
     68     {
     69         int x=dfs(S,INF);//多路增广,每次初始为最大的流量 
     70         while (x)
     71         {
     72             res+=x;
     73             x=dfs(S,INF);
     74         }
     75     }
     76     return res;
     77 }
     78 int main()
     79 {
     80     while (~scanf("%d%d",&n,&m))
     81     {
     82         cnt=1;//这是一个技巧,对反向边的构造有帮助 
     83         memset(head,-1,sizeof(head));
     84         int tt,x;
     85         for (int i=1;i<=n;i++)
     86         {
     87             scanf("%d",&tt);
     88             for (int j=1;j<=tt;j++)
     89             {
     90                 scanf("%d",&x);
     91                 x+=n;
     92                 add(i,x,1);
     93                 add(x,i,0);
     94             }
     95         }
     96         S=n+m+1;
     97         T=n+m+2;
     98         //网络流不同于匈牙利算法,每个点的标号要区分开 
     99         for (int i=1;i<=n;i++)
    100         {
    101             add(S,i,1);
    102             add(i,S,0);    
    103         }    
    104         for (int i=1;i<=m;i++)
    105         {
    106             add(i+n,T,1);
    107             add(T,i+n,0);
    108         }
    109         printf("%d
    ",dinic());
    110     }
    111 }
  • 相关阅读:
    layer备忘
    Java中遍历Map对象的4种方法
    为什么Java中1000==1000为false而100==100为true?
    linux系统安装psycopg2
    centos7源码安装mysql5.7
    Azure Sql
    javascript和jQuery动态修改css样式的方法
    Git early EOF index-pack failed 问题
    C# 多线程——SemaphoreSlim的使用
    Docker 可视化
  • 原文地址:https://www.cnblogs.com/fengzhiyuan/p/7163067.html
Copyright © 2011-2022 走看看