给定一个包含 n n n 个点 m m m 条边的有向图,并给定每条边的容量,边的容量非负。
图中可能存在重边和自环。求从点 S S S 到点 T T T 的最大流。
输入格式
第一行包含四个整数
n
,
m
,
S
,
T
n,m,S,T
n,m,S,T。
接下来
m
m
m 行,每行三个整数
u
,
v
,
c
u,v,c
u,v,c,表示从点
u
u
u 到点
v
v
v 存在一条有向边,容量为
c
c
c。
点的编号从 1 1 1 到 n n n。
输出格式
输出点 S S S 到点 T T T 的最大流。
如果从点 S S S 无法到达点 T T T 则输出 0 0 0。
数据范围
2
≤
n
≤
1000
2 \le n \le 1000
2≤n≤1000,
1
≤
m
≤
10000
1 \le m \le 10000
1≤m≤10000,
0
≤
c
≤
10000
0 \le c \le 10000
0≤c≤10000,
S
≠
T
S \neq T
S=T
输入样例:
7 14 1 7
1 2 5
1 3 6
1 4 5
2 3 2
2 5 3
3 2 2
3 4 3
3 5 3
3 6 7
4 6 5
5 6 1
6 5 1
5 7 8
6 7 7
输出样例:
14
思路
割的表示 [ S , T ] [S,T] [S,T]。
割点容量只有正向边,且割是可以任意划分的,分割方式有 2 n − 2 2^{n-2} 2n−2 种。
最小割就是在所有割的集合种容量最小的割。
注意:割的概念是指在如上图所示 S S S 和 T T T 之间的边。
这个是恒成立的。(你仔细想想)
所以最小割==最大流。
经验:最小割比较抽象,建议直接就按定义来。
模板
//最大流==最小割
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N = 1e4+10,M = 2e5+10,INT = 1e8;
int e[M],ne[M],f[M],h[N],idx;
int cur[N],d[N];
int n,m,S,T;
void add(int a,int b,int c){
e[idx]=b,f[idx]=c,ne[idx]=h[a],h[a]=idx++;
e[idx]=a,f[idx]=0,ne[idx]=h[b],h[b]=idx++;
}
bool bfs(){
queue<int>q;
q.push(S);
memset(d,-1,sizeof d);
d[S]=0,cur[S]=h[S];
while(q.size()){
int t=q.front();
q.pop();
for(int i=h[t];~i;i=ne[i]){
int ver=e[i];
if(d[ver]==-1&&f[i]){
d[ver]=d[t]+1;
cur[ver]=h[ver];
if(ver==T)return true;
q.push(ver);
}
}
}
return false;
}
int find(int u,int lim){
if(u==T)return lim;
int flow=0;
for(int i=cur[u];~i&&flow<lim;i=ne[i]){
int ver=e[i];
cur[u]=i;
if(d[ver]==d[u]+1&&f[i]){
int t=find(ver,min(f[i],lim-flow));
if(!t)d[ver]=-1;
f[i]-=t,f[i^1]+=t,flow+=t;
}
}
return flow;
}
int dinic(){
int r=0,flow;
while(bfs())while(flow=find(S,INT))r+=flow;
return r;
}
int main(){
cin>>n>>m>>S>>T;
memset(h,-1,sizeof h);
for(int i=1;i<=m;i++){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
cout<<dinic();
return 0;
}