【Dinic模板】【洛谷P3376】

本文深入介绍了Dinic算法,一种高效的最大流算法。该算法通过构建层次图并利用DFS寻找增广路来逐步优化网络流,确保了良好的时间复杂度O(n^2*m)。文章详细解释了算法的实现细节,并提供了完整的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值