Dinic
Dinic算法是网络流最大流的优化算法之一,每一步对原图进行分层,然后用DFS求增广路。时间复杂度是O(n^2*m),Dinic算法最多被分为n个阶段,每个阶段包括建层次网络和寻找增广路两部分。
Dinic算法的思想是分阶段地在层次网络中增广。它与最短增广路算法不同之处是:最短增广路每个阶段执行完一次BFS增广后,要重新启动BFS从源点Vs开始寻找另一条增广路;而在Dinic算法中,只需一次DFS过程就可以实现多次增广。
算法介绍
层次图:
层次图,就是把原图中的点按照点到源的距离分“层”,只保留不同层之间的边的图。
算法流程:
1、根据残量网络计算层次图。
2、在层次图中使用DFS进行增广直到不存在增广路。
3、重复以上步骤直到无法增广。
时间复杂度:
因为在Dinic的执行过程中,每次重新分层,汇点所在的层次是严格递增的,而n个点的层次图最多有n层,所以最多重新分层n次。在同一个层次图中,因为每条增广路都有一个瓶颈,而两次增广的瓶颈不可能相同,所以增广路最多m条。搜索每一条增广路时,前进和回溯都最多n次,所以这两者造成的时间复杂度是O(nm);而沿着同一条边(i,j)不可能枚举两次,因为第一次枚举时要么这条边的容量已经用尽,要么点j到汇不存在通路从而可将其从这一层次图中删除。综上所述,Dinic算法时间复杂度的理论上界是O(n^2*m)。
实现
首先对每条弧存一条反向弧,初始流量为0,当正向弧剩余流量减少时,反向弧剩余流量随之增加,这样就为每条弧提供了一个反悔的机会,可以让一个流沿反向弧退回而去寻找更优的路线。对于一个网络流图,用bfs将图分层,只保留每个点到下一个层次的弧,目的是减少寻找增广路的代价。对于每一次可行的增广操作,用dfs的方法寻找一条由源点到汇点的路径并获得这条路径的流量c。根据这条路径修改整个图,将所经之处正向边流量减少c,反向边流量增加c。如此反复直到bfs找不到可行的增广路线。
当前弧优化:
对于一个节点x,当它在dfs中走到了第i条弧时,前i-1条弧到汇点的流一定已经被流满而没有可行的路线了。那么当下一次再访问x节点的时候,前i-1条弧就可以被删掉而没有任何意义了。所以我们可以在每次枚举节点x所连的弧时,改变枚举的起点,这样就可以删除起点以前的所有弧以达到优化的效果。
代码
#include<bits/stdc++.h>
#define INF 10000000
using namespace std;
int head[10005],nex[200005],tail[200005];
int cap[200005],tp=-1,dep[10005];
int fir[10005];
int n,m,s,t,a,b,v;
int dfs(int x,int now){
if(!now||x==t) return now;
int c=0;
for(int i=head[x];i!=-1;i=nex[i]){
if(cap[i]&&dep[tail[i]]==dep[x]+1){
int f=dfs(tail[i],min(now,cap[i]));
c+=f;now-=f;
cap[i]-=f;cap[i^1]+=f;
}
}
return c;
}
inline bool bfs(){
memset(dep,0,sizeof(dep));
dep[s]=1;
queue<int>q;
q.push(s);
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=head[x];i!=-1;i=nex[i]){
if(cap[i]&&!dep[tail[i]]){
dep[tail[i]]=dep[x]+1;
q.push(tail[i]);
}
}
}
return dep[t];
}
inline void add(int x,int y,int v){
nex[++tp]=head[x];
head[x]=tp;
tail[tp]=y;
cap[tp]=v;
}
inline int Dinic(){
int c=0;
while(bfs())c+=dfs(s,INF);
return c;
}
int main(){
memset(head,-1,sizeof(head));
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=1;i<=m;++i){
scanf("%d%d%d",&a,&b,&v);
add(a,b,v);add(b,a,0);
}
printf("%d",Dinic());
return 0;
}