网络流之最大流算法

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

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\n",dinic());
110     }
111 }

 

posted @ 2017-07-13 20:39  Kaiser-  阅读(673)  评论(0编辑  收藏  举报