首先是网络流中的一些定义:
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 }