Dinic的模板加上简单理解

Dinic是用于求网络流中的最大流的经典算法

它的大致思路可以理解为三个步骤

第一,判断源点S到汇点T是否联通  ,若联通,求出S到T的最短路径(相连的两点之间距离设为1)

第二,在第一步中求出了一条最短路径,那我们就用个笨方法,找出这条最短路径上流量最小的边    假设这条边流量为 z ,显然 有z的流量可以通过这条路径从S流到汇点T,  那么答案ans+=z       ,由于我们这条路径上流过了z的流量,所以这条路径上所有边的流量上限都要减去 z

第三,不断重复上述步骤,直到S与T不再联通,那么ans就是最大流了。

需要注意的是为了保证Dinic的正确性,Dinic支持反悔操作, 即:当我们后续发现之前流过的边并非最优,我们就可以反向流回来,抵消掉之前的操作 ,怎么才能方便在两条边之间加减呢

我们来看下面的操作 对于任意i       i^1=?  当i=2时 i^1=3   当i=3时 i^1=2    当i=4时 i^1=5

当i=5时 i^1=4   你发现了什么!  根据这个原理!我们就可以将一对相反边存在类似于(2,3) 

(4,5)这两个连续的位置! 这样e[i]+=tmp时 只需要e[i^1]-=tmp!

有两个小优化在注视中标注了

#include<bits/stdc++.h>
using namespace std;
#define ll long long   //防止爆int
#define _for(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
const int N=1e5+50;   //记得大小开够
const ll inf=1e18;
int in(){
    int ans=0,f=1;char c=getchar();
    while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
    while(isdigit(c)){ans=ans*10+c-'0';c=getchar();}
    return ans*f;
}
int cnt=1,hed[N];  //优化一:这里cnt从1开始 ,主要目的就是利用i^1构造相反边
struct nod{
    int to,z,nxt;
}e[N];
void add(int x,int y,int z){
    e[++cnt]=(nod){y,z,hed[x]};hed[x]=cnt;
}

int n,m,S,T,dis[N];
queue<int>q;
bool bfs(){
    //bfs用于判断S与T是否联通
    _for(i,0,n)dis[i]=0; dis[S]=1;
    q.push(S);
    while(!q.empty()){
        int x=q.front();q.pop();
        for(int i=hed[x];i;i=e[i].nxt){
            int y=e[i].to,z=e[i].z;
            if(dis[y]||z<=0)continue;
            dis[y]=dis[x]+1;  q.push(y);
        }
    }
    return dis[T];
}

ll dfs(int x,ll exp){
    if(x==T)return exp; //搜索到汇点直接返回
    ll flow=0,tmp=0;    
    for(int i=hed[x];i&&exp;i=e[i].nxt){ //当exp用完时可以退出了
        int y=e[i].to;ll z=e[i].z;
        if(dis[y]!=dis[x]+1||z<=0)continue;
        tmp=dfs(y,min(z,exp));
        flow+=tmp;exp-=tmp; 
        e[i].z-=tmp;
        e[i^1].z+=tmp;
    }
    if(flow==0)dis[x]=0;  //优化二:如果当前我们点对流量没有贡献,那么我们后面不用再管它
    return flow;
}
int main(){
    n=in();m=in();S=in();T=in();
    for(int x,y,z,i=1;i<=m;i++){
        x=in();y=in();z=in();
        add(x,y,z);add(y,x,0);
    }
    ll ans=0;
    while(bfs())ans+=dfs(S,inf);  //先判联通,后计算
    printf("%lld\n",ans);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值