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;
}