在《算法竞赛入门经典(第二版)》中介绍了Edmonds-Karp算法(简称EK算法),这种算法虽然易于理解但效率不够高,无法满足竞赛的需求。因此这里给出效率比较快一点的Dinic算法。
(1)概述: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也就是阻塞流。可以直接理解为简单路径上的最小残量值。
因此,该算法第一次就是将上述那条简单路径的所有流量值加上4(这一过程叫“增广”),然后重复原来的过程直到s到t的简单路径不存在为止。可以证明,Dinic算法的时间复杂度的理论上界是O(N^2*M)(N是结点数,M是边数),但实际上Dinic算法比这个理论上界好得多。如果所有边容量均为1,那么时间复杂度是O(min(N^0.67,M^0.5)*M);对于二分图最大匹配这样的特殊图,时间复杂度是O(N^0.5*M)。
struct Dinic { int n,m,s,t;//结点数,边数(包括反向弧),源点编号,汇点编号 vector<Edge>edges;//边表,dges[e]和dges[e^1]互为反向弧 vector<int>G[N];//邻接表,G[i][j]表示结点i的第j条边在e数组中的编号 bool vis[N]; //BFS的使用 int d[N]; //从起点到i的距离 int cur[N]; //当前弧下标 void addedge(int from,int to,int cap) { edges.push_back((Edge){from,to,cap,0}); edges.push_back((Edge){to,from,0,0}); int m=edges.size(); G[from].push_back(m-2); G[to].push_back(m-1); } bool bfs() { memset(vis,0,sizeof(vis)); queue<int>Q; Q.push(s); d[s]=0; vis[s]=1; while(!Q.empty()) { int x=Q.front();Q.pop(); for(int i=0;i<G[x].size();i++) { Edge&e=edges[G[x][i]]; if(!vis[e.to]&&e.cap>e.flow)//只考虑残量网络中的弧 { vis[e.to]=1; d[e.to]=d[x]+1; Q.push(e.to); } } } return vis[t]; } int dfs(int x,int a)//x表示当前结点,a表示目前为止的最小残量 { if(x==t||a==0)return a;//a等于0时及时退出,此时相当于断路了 int flow=0,f; for(int&i=cur[x];i<G[x].size();i++)//从上次考虑的弧开始,注意要使用引用,同时修改cur[x] { Edge&e=edges[G[x][i]];//e是一条边 if(d[x]+1==d[e.to]&&(f=dfs(e.to,min(a,e.cap-e.flow)))>0) { e.flow+=f; edges[G[x][i]^1].flow-=f; flow+=f; a-=f; if(!a)break;//a等于0及时退出 } } return flow; } int Maxflow(int s,int t)//主过程 { this->s=s,this->t=t; int flow=0; while(bfs())//不停地用bfs构造分层网络,然后用dfs沿着阻塞流增广 { memset(cur,0,sizeof(cur)); flow+=dfs(s,INF); } return flow; }<pre class="cpp" name="code">}
另外补充上利用链表实现的Dinic算法,这样的好处是代码更精简,同时可以通过结点查找某条边
struct Dinic { static const int N = 510, M = 100010; int head[N];//链表头部,存储以u开始的边序号 int en[M];//记录v int Next[M];//记录下一条边的序号 int cap[M];//记录容量 int tot;//边数 void clear() { memset(head, 0, sizeof(head)); tot = 1; } void add(int u, int v, int w) { en[++tot] = v; Next[tot] = head[u]; head[u] = tot; cap[tot] = w; } void Add(int u, int v, int w){ add(u, v, w); add(v, u, 0); } int d[N], cur[N]; int n, s, t; bool bfs() { queue<int>q; memset(d, -1, sizeof(d)); d[s] = 0; q.push(s); while (!q.empty()) { int x = q.front(); q.pop(); for (int k = head[x],v; k; k = Next[k]) if (cap[k] && d[v = en[k]]==-1) { d[v] = d[x] + 1; q.push(v); } } return d[t] != -1; } int dfs(int x, int y) { if (x == t || !y)return y; int z = y; for (int&k = cur[x],v; k; k = Next[k]) if (cap[k] && d[v = en[k]] == d[x] + 1) { int w = dfs(v, min(cap[k], z)); cap[k] -= w;//流量增大意味着净容量减少 cap[k ^ 1] += w;//反向边容量表示了正向边的流量 z -= w; if (!z)break; } if (z == y)d[x] = -1; return y - z; } int Maxflow(int s, int t) { this->s = s, this->t = t; int flow = 0; while (bfs()) { for (int i = 1; i <= n; i++) cur[i] = head[i]; flow += dfs(s, INF); } return flow; } };