求最大流的本质,就是不停的寻找增广路径。直到找不到增广路径为止。
对于这个一般性的过程,Dinic算法的优化如下:
(1)
Dinic算法首先对图进行一次BFS,然后在BFS生成的层次图中进行多次DFS。
层次图的意思就是,只有在BFS树中深度相差1的节点才是连接的。
这就切断了原有的图中的许多不必要的连接。很牛逼!
这是需要证明的,估计证明也很复杂。
(2)
除此之外,每次DFS完后,会找到路径中容量最小的一条边。
在这条边之前的路径的容量是大于等于这条边的容量的。
那么从这条边之前的点,可能引发出别的增广路径。
比如说 S -> b -> c -> d -> T 是一条增广路径,容量最小的边是 b -> c。
可能存在一条 S -> b -> e -> f -> g -> T 这样的增广路径。
这样的话,在找到第一条增广路径后,只需要回溯到 b 点,就可以继续找下去了。
这样做的好处是,避免了找到一条路径就从头开始寻找另外一条的开销。
也就是再次从 S 寻找到 b 的开销。
这个过程看似复杂,但是代码实现起来很优雅,因为它的本质就是回溯!
(3)
在同一次 DFS 中。如果从一个点引发不出任何的增广路径,就将这个点在层次图中抹去。Di
Dinic :
#include<iostream> #include<cstdio> #include<cstring> #include<queue> using namespace std; const int VM=220; const int INF=0x3f3f3f3f; int n,m,src,des; int map[VM][VM],dep[VM]; //dep[i]表示当前点到起点src的层数 int BFS(){ // 重新建图(按层数建图) queue<int> q; while(!q.empty()) q.pop(); memset(dep,-1,sizeof(dep)); dep[src]=0; q.push(src); while(!q.empty()){ int u=q.front(); q.pop(); for(int v=1;v<=m;v++) if(map[u][v]>0 && dep[v]==-1){ // 如果可以到达且还没有访问 dep[v]=dep[u]+1; q.push(v); } } return dep[des]!=-1; } int DFS(int u,int minx){ // 查找路径上的最小的流量 if(u==des) return minx; int tmp; for(int v=1;v<=m;v++) if(map[u][v]>0 && dep[v]==dep[u]+1 && (tmp=DFS(v,min(minx,map[u][v])))){ map[u][v]-=tmp; //正向减少 map[v][u]+=tmp; //反向增加 return tmp; } return 0; } int Dinic(){ int ans=0,tmp; while(BFS()){ while(1){ tmp=DFS(1,INF); if(tmp==0) break; ans+=tmp; } } return ans; } int main(){ //freopen("input.txt","r",stdin); while(~scanf("%d%d",&n,&m)){ memset(map,0,sizeof(map)); int u,v,w; for(int i=0;i<n;i++){ scanf("%d%d%d",&u,&v,&w); map[u][v]+=w; //防止有重边 } src=1, des=m; printf("%d\n",Dinic()); } return 0; }
EK :
#include<iostream> #include<cstdio> #include<cstring> #include<queue> using namespace std; const int VM=220; const int INF=0x3f3f3f3f; int n,m,max_flow; //max_flow是最大流 int map[VM][VM],flow[VM][VM]; // map[i][j]是每条边的容量,flow[i][j]是每条边的流量 int res[VM],pre[VM]; //res[]是每个点的剩余流量,pre[]是每个点的父亲 int EK(int src,int des){ max_flow=0; queue<int> q; while(!q.empty()) q.pop(); memset(flow,0,sizeof(flow)); //最开始每条边的流量都是0 while(1){ memset(res,0,sizeof(res)); //残余流量得变0,一开始所有点都没流入对吧 res[src]=INF; //源点嘛,剩余流量无限是必须的... q.push(src); //从源点开始进行BFS找增广路 int u,v; while(!q.empty()){ u=q.front(); q.pop(); for(v=1;v<=m;v++) //遍历所有点,找可行边 if(!res[v] && map[u][v]>flow[u][v]){ //该点剩余流量为0 且 容量大于流量,也就是找到了新的结点 pre[v]=u; //找到新结点,父节点得记录一下吧 q.push(v); res[v]=min(res[u],map[u][v]-flow[u][v]); //如果u的剩余流量能填满uv就填满,不能的话就把u这点的流量全部流向uv } } if(res[des]==0) //如果当前已经是最大流,汇点没有残余流量 return max_flow; for(u=des;u!=src;u=pre[u]){ //如果还能增广,那么回溯,从汇点往回更新每条走过的边的流量 flow[pre[u]][u]+=res[des]; //更新正向流量 (注意这里更新的是流量,而不是容量) flow[u][pre[u]]-=res[des]; //更新反向流量 } max_flow+=res[des]; } } int main(){ //freopen("input.txt","r",stdin); while(~scanf("%d%d",&n,&m)){ memset(map,0,sizeof(map)); memset(pre,0,sizeof(pre)); int u,v,w; while(n--){ scanf("%d%d%d",&u,&v,&w); map[u][v]+=w; //有重边 } printf("%d\n",EK(1,m)); } return 0; }