网络是指一个有向图 G = ( V , E ) G=(V,E) G=(V,E),这个有向图有两个特殊节点:源点 S S S和汇点 T T T。每条有向边 ( x , y ) ∈ E (x,y)\in E (x,y)∈E有一个权值 c ( x , y ) c(x,y) c(x,y),称为边的容量。若 ( x , y ) ∉ E (x,y)\notin E (x,y)∈/E。则 c ( x , y ) = 0 c(x,y)=0 c(x,y)=0。
我们用 f ( x , y ) f(x,y) f(x,y)表示 ( x , y ) (x,y) (x,y)上的流量, c ( x , y ) − f ( x , y ) c(x,y)-f(x,y) c(x,y)−f(x,y)为边的剩余容量。通常用 f ( x , y ) / c ( x , y ) f(x,y)/c(x,y) f(x,y)/c(x,y)表示边上的流量与容量。
若一个流对每一发点满足总流出量与总流入量之差不大于提供能力,对每一收点满足总流入量与总流出量之差不小于接收能力,则称这个流为可行流。
对于一个可行流,必须满足一下条件:
1.容量限制:每条边的流量不能超过每条边容量。
2.流量守恒:除源点与汇点外,进入的流量等于流出的流量。
最大流:从源点流向汇点的最大流量
增广路:一条从源点到汇点的所有边剩余容量 ≥ 0 \ge 0 ≥0的路径
残留网:由网络中的所有结点和剩余容量 > 0 \gt 0 >0的边构成的子图,这里的边包括有向边和其反向边。
实际上,找最大流的过程就是不断从源点到汇点找增广路的过程。
我们在建图时对每条有向边
(
x
,
y
)
(x,y)
(x,y)都构建一条反向边
(
y
,
x
)
(y,x)
(y,x),初始容量
c
(
y
,
x
)
=
0
c(y,x)=0
c(y,x)=0。构建反向边的目的是为了提供一个“退流管道”,一旦前面的增广路堵死可行流,可以通过“退流管道”退流,提供了“后悔机制”。如下图所示:(图中红色的边就是退流管道)



我们可以发现,退流管道的容量和原方向边的容量的和等于最初始的边的容量。有了退流管道的帮助,我们就不必担心增广路堵死可行流的情况,就能找出网络最大流了。
1.
b
f
s
bfs
bfs找增广路:
我们需要一个pre数组来存储当前到的点的前驱边。(方便EK逆序更新残留网)。
2.
E
K
(
)
EK()
EK()找最大值:
(1).逆序更新残留网,容量“此消彼长”。
(2).累加可行流,然后返回最大值。
#include
#define in read()
#define cs const
#define re register
#define int long long
using namespace std;
cs int N=205;
cs int M=5050<<1;
cs int inf=1e9;
struct edge{
int v,c,ne;
}e[M];
int h[N],tot=1;
int mf[N],pre[N];
int n,m,S,T;
inline int read(){
int x=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return x*f;
}
inline void add(int a,int b,int c){
e[++tot]=(edge){b,c,h[a]};
h[a]=tot;
}
inline bool bfs(){
memset(mf,0,sizeof mf);
queue<int>q;
q.push(S),mf[S]=inf;
while(!q.empty()){
int u=q.front();q.pop();
for(re int i=h[u];i;i=e[i].ne){
int v=e[i].v;
if(!mf[v] and e[i].c){
pre[v]=i;
mf[v]=min(mf[u],e[i].c);
q.push(v);
if(v==T)return true;
}
}
}
return false;
}
inline int EK(){
int flow=0;
while(bfs()){
int v=T;
while(v!=S){
int i=pre[v];
e[i].c-=mf[T];
e[i^1].c+=mf[T];
v=e[i^1].v;
}
flow+=mf[T];
}
return flow;
}
signed main(){
n=in,m=in,S=in,T=in;
for(re int i=1;i<=m;i++){
int u=in,v=in,c=in;
add(u,v,c),add(v,u,0);
}
cout<<EK()<<'\n';
return 0;
}
我们来浅浅地估计一下 E K EK EK算法的复杂度,每一次找增广路最多为 m m m,最多需要找 n n n次,总复杂度是 O ( n m 2 ) O(nm^2) O(nm2)的,但实际上复杂度并没有那么高,在面对有些较大的数据时任然不会 T T T的。