Dinic算法
简介:
相较 Edmonds-Karp 算法而言,为防止每进行一次增广,都要做一遍BFS,时间复杂度过高,用于减少所作 BFS 次数。
因而 Dinic 算法在 EK 算法的基础上增加了分层图的概念,根据从s到各个点的最短距离的不同,把整个图分层。
过程:
- 先利用BFS对残余网络分层,一个节点的深度,就是源点到它最少要经过的边数。
- 利用BFS对残余网络分层,分完层后,利用DFS从前一层向后一层反复寻找增广路。
分完层后,从源点开始,用DFS从前一层向后一层反复寻找增广路(即要求DFS的每一步都必须要走到下一层的节点)。 - 在分层时,只要进行到汇点的层次数被算出即可停止,因为按照该DFS的规则,和汇点同层或更下一层的节点,是不可能走到汇点的。
- DFS过程中,要是碰到了汇点,则说明找到了一条增广路径。此时要增加总流量的值,消减路径上各边的容量,并添加反向边,即所谓的进行增广。
- DFS找到一条增广路径后,并不立即结束,而是回溯后继续DFS寻找下一个增广路径。
- 当最上层的节点如果回溯到源点而且无法继续往下走了,DFS结束,在此次DFS过程中,可以找到多条增广路径。
- DFS结束后,对残余网络再次进行分层,然后再进行DFS当残余网络的分层操作无法算出汇点的层次(即BFS到达不了汇点)时,算法结束,最大流求出。
时间复杂度:
普通情况下, Dinic算法时间复杂度为O(V2E)
在二分图中, Dinic算法时间复杂度为O(√VE)
模板题:Drainage Ditches
题意:
n 是农夫约翰挖的沟的数量, m 是这些沟渠的交叉点数量,
交叉口 1 是池塘,交叉点 M 是河流。
水从这条沟从 si 号流到 ei 号, ci 是水流通过沟渠的最大速率。
求水渠中所能流过的水的最大容量。
代码:
#include<iostream>
#include<string.h>
#include<queue>
#include<cmath>
using namespace std;
#define INF 0x3f3f3f3f
const int MAX=210;
struct node
{
int c;//速率
int f;//逆流量
};
int sx,ex;//sx和ex分别代表源点和汇点
int pre[MAX];//前驱
node map[MAX][MAX];
int n,m;
bool BFS()//BFS搜索层次网络
{
memset(pre,0,sizeof(pre));
queue<int> q;
q.push(sx);
pre[sx]=1;
while(!q.empty())
{
int start=q.front();
q.pop();
for(int i=1;i<=n;i++)
{
if(!pre[i]&&map[start][i].c-map[start][i].f)
{
pre[i]=pre[start]+1;
q.push(i);
}
}
}
return pre[ex]!=0;
}
int dinic(int pos,int flow)//pos是顶点号,flow是当前顶点所能得到的流量
{
int f=flow;
if(pos==ex)
return flow;
for(int i=1;i<=n;i++)
{
if(map[pos][i].c-map[pos][i].f&&pre[pos]+1==pre[i])
{
int use=map[pos][i].c-map[pos][i].f;//可用水量
int t=dinic(i,min(use,flow));
map[pos][i].f+=t;
map[i][pos].f-=t;
flow-=t;//此顶点得到的流量减去改变量
}
}
return f-flow;
}
int slove()
{
int sum=0;
while(BFS())
{
sum+=dinic(sx,INF);
}
return sum;
}
int main()
{
int u,v,w;
while(cin>>m>>n)
{
sx=1;
ex=n;
memset(map,0,sizeof(map)) ;
for(int i=1;i<=m;i++)
{
cin>>u>>v>>w;
map[u][v].c+=w;
}
cout<<slove()<<endl;
}
return 0;
}
ISAP算法
简介:
同样是基于分层思想的最大流算法,而有所不同的是,它省去了 Dinic 每次增广后需要重新构建分层图的麻烦,而是在每次增广完成后自动更新每个点的标号。(也就是所在的层)
未结束,待更新
参考:[网络流]学习笔记:一次理解网络流!