dinic算法是一种对karp算法的有效优化,所以在介绍dinic之前得先看看karp算法,比如本人的一篇karp算法的blog.
dinic算法在karp算法的基础上增加了一个分层图的优化,分层图就是一张满足若从u搜到了v,那么就满足deep[v]=deep[u]+1的有向无环图.
之后dinic算法不是直接在剩余网络上做dfs找增广路,而是在剩余网络的分层图上.
而这时dinic算法就可以在一次扫描中,同时扩展出多条增光路.
其实就是在dfs回溯的过程中,若当前跑到当前点还有剩余容量,就可以直接同时再次扩展.
那么dinic算法的时间复杂度为O(n^2*m),但是上界很松,一般来说可以处理10^4~10^5级的数据.
由于是从karp算法优化而来,所以dinic算法还是要存反向边.
具体实现就看代码.
主循环控制:
int maxflow(int S,int T){ int sum=0,minf; while (1){ //while(1) 控制循环 if (!bfs(S,T)) return sum; //bfs求出分层图,顺便判断是否有增广路 minf=dfs(S,T,INF); //dfs求出流量 if (minf) sum+=minf; //若流量不为0,加入 else return sum; //流量为0,说明没有增广路,返回最大流 } }
bfs求分层图:
int deep[N+1]; int q[N+1]={0},h,t; bool bfs(int S,int T){ for (int i=0;i<=n;i++) deep[i]=0; //初始化深度为0 h=t=1;q[1]=S;deep[S]=1; while (h<=t){ for (int i=lin[q[h]];i;i=e[i].next) if (!deep[e[i].y]&&e[i].v){ //若未计算过深度且这条边不能是空的 q[++t]=e[i].y; //入队一个节点 deep[q[t]]=deep[q[h]]+1; //计算深度 } ++h; } if (deep[T]) return true; else return false; }
dfs找增广路:
int dfs(int start,int T,int minf){ //从start到T的总流量,目前前面可以流过来的最大流量minf,返回的是当前结点最大可以流到汇点的流量(满足必须要前面有足够的流) if (start==T) return minf; //若到了汇点直接返回前面流过来的流量 int sum=0,flow=0; for (int i=lin[start];i;i=e[i].next) if (e[i].v&&deep[start]+1==deep[e[i].y]) { //若满足边权不为0且满足分层图的性质 flow=dfs(e[i].y,T,min(minf,e[i].v)); //继续找增广路 if (!flow) deep[e[i].y]=0; //去掉已经增广完的点 sum+=flow; //统计最大流 minf-=flow; //剩余容量 e[i].v-=flow;e[i^1].v+=flow; //更新剩余容量 if (!minf) return sum; //若前面已经流完了,直接返回 } return sum; //返回最大流量 }
总的dinic算法代码如下:
int deep[N+1]; int q[N+1]={0},h,t; bool bfs(int S,int T){ for (int i=0;i<=n;i++) deep[i]=0; //初始化深度为0 h=t=1;q[1]=S;deep[S]=1; while (h<=t){ for (int i=lin[q[h]];i;i=e[i].next) if (!deep[e[i].y]&&e[i].v){ //若未计算过深度且这条边不能是空的 q[++t]=e[i].y; //入队一个节点 deep[q[t]]=deep[q[h]]+1; //计算深度 } ++h; } if (deep[T]) return true; else return false; } int dfs(int start,int T,int minf){ //从start到T的总流量,目前前面可以流过来的最大流量minf,返回的是当前结点最大可以流到汇点的流量(满足必须要前面有足够的流) if (start==T) return minf; //若到了汇点直接返回前面流过来的流量 int sum=0,flow=0; for (int i=lin[start];i;i=e[i].next) if (e[i].v&&deep[start]+1==deep[e[i].y]) { //若满足边权不为0且满足分层图的性质 flow=dfs(e[i].y,T,min(minf,e[i].v)); //继续找增广路 if (!flow) deep[e[i].y]=0; //去掉已经增广完的点 sum+=flow; //统计最大流 minf-=flow; //剩余容量 e[i].v-=flow;e[i^1].v+=flow; //更新剩余容量 if (!minf) return sum; //若前面已经流完了,直接返回 } return sum; //返回最大流量 } int maxflow(int S,int T){ int sum=0,minf; while (1){ //while(1) 控制循环 if (!bfs(S,T)) return sum; //bfs求出分层图,顺便判断是否有增广路 minf=dfs(S,T,INF); //dfs求出流量 if (minf) sum+=minf; //若流量不为0,加入 else return sum; //流量为0,说明没有增广路,返回最大流 } }
当然这个算法其实还可以加一个小优化:当前弧优化.
当前弧优化的意思就是说每次开始跑邻接表遍历不是从第一条边开始跑而是从上一次点i遍历跑到的点.
我们用cur[i]表示这个点,之后每次建完分层图之后都要进行初始化,且见分层图时不存在当前弧优化.
那么修改过后的dinic算法如下:
int deep[N+1]; int q[N+1]={0},h,t; int cur[N+1]; bool bfs(int S,int T){ for (int i=0;i<=n;i++) deep[i]=0; //初始化深度为0 h=t=1;q[1]=S;deep[S]=1; while (h<=t){ for (int i=lin[q[h]];i;i=e[i].next) if (!deep[e[i].y]&&e[i].v){ //若未计算过深度且这条边不能是空的 q[++t]=e[i].y; //入队一个节点 deep[q[t]]=deep[q[h]]+1; //计算深度 } ++h; } if (deep[T]) return true; else return false; } int dfs(int start,int T,int minf){ //从start到T的总流量,目前前面可以流过来的最大流量minf,返回的是当前结点最大可以流到汇点的流量(满足必须要前面有足够的流) if (start==T) return minf; //若到了汇点直接返回前面流过来的流量 int sum=0,flow=0; for (int &i=cur[start];i;i=e[i].next) //当前弧优化,运用指针在修改i的同时,将cur[start]顺便修改 if (e[i].v&&deep[start]+1==deep[e[i].y]) { //若满足边权不为0且满足分层图的性质 flow=dfs(e[i].y,T,min(minf,e[i].v)); //继续找增广路 if (!flow) deep[e[i].y]=0; //去掉已经增广完的点 sum+=flow; //统计最大流 minf-=flow; //剩余容量 e[i].v-=flow;e[i^1].v+=flow; //更新剩余容量 if (!minf) return sum; //若前面已经流完了,直接返回 } return sum; //返回最大流量 } int maxflow(int S,int T){ int sum=0,minf; while (1){ //while(1) 控制循环 if (!bfs(S,T)) return sum; //bfs求出分层图,顺便判断是否有增广路 for (int i=1;i<=n;i++) cur[i]=lin[i]; //当前弧的初始化 minf=dfs(S,T,INF); //dfs求出流量 if (minf) sum+=minf; //若流量不为0,加入 else return sum; //流量为0,说明没有增广路,返回最大流 } }