最大流可以看做是把一些东西从源点s送到汇点t,可以从其他的点中转,每条边最多只能输送一定的物品,求最多可以把多少东西从s送到t,这样的问题就是最大流问题。
节点1为源点,节点5位汇点
每一条边上的数字即为这条边最多能输送的数量,也称为容量。(对于不存在的边,容量为0)
这个图能够求出的最大流为24。
这时,实际运送的物品量即为流量。
有些边的流量不一定等于容量,也就是还可以在这条路上再流多几个物品,这时,容量减去流量的值,即为残量
残量会单独构成网络,但不一定联通s和t,这样的网络称作残量网络。
那么,应该怎么求最大流呢?
这里介绍一个最基础的算法,增广路算法。
在图上,从s开始,任意取一条路来走,走到t,增加流量。重复这样的操作。但是很快,我们可以发现,这样的做法不一定是最优的做法。所以,我们要给它一个改进的机会。
在图上建立反向弧,将容量设置为0,当从节点u流向节点v时,反向弧的流量也相应等于这条弧的流量的相反数。
那么,通过搜索,每一次寻找一条路径,使得流向汇点的总流量增加,这个过程叫做增广,走过的路为增广路。
可以证明,通过这个过程,经过多次的增广,必然会陷入不能增广的情况,这时,流向汇点的总流量即为最大流。
只要残量网络中s和t是连通的,必然会得到一条增广路径。
找路径最简单的办法就是用DFS,但是对于一些具有刁钻数据的网络流题目,DFS可能会空间溢出或者超时,所以使用到BFS,也就是题目中说的Edmonds-Karp算法。
代码模板(来自算法竞赛入门第二版(刘汝佳)):
struct edge{
int from,to,cap,flow;
edge(int u,int v,int c,int f):from(u),to(v),cap(c),flow(f){}
};
struct Edmonds_Karp{
int n,m;
vector<edge>edges;//边数的两倍
vector<int>g[maxn];//邻接表,g[i][j]表示节点i的第j条边在e数组中的序号
int a[maxn];//当起点到i的可改进量
int p[maxn];//最短路树上p的入弧编号
void init(int n){
for(int i=0;i<n;i++) g[i].clear();
edges.clear();
}
void addedge(int from,int to,int cap){
edges.push_back(edge(from,to,cap,0));
edges.push_back(edge(to,from,0,0));//反向弧
m=edges.size();
g[from].push_back(m-2);
g[to].push_back(m-1);
}
int Maxflow(int s,int t){
int flow=0;
for(;;){
memset(a,0,sizeof(a));
queue<int>q;
q.push(s);
a[s]=inf;
while(!q.empty()){
int x=q.front();q.pop();
for(int i=0;i<(int)g[x].size();i++){
edge&e=edges[g[x][i]];
if(!a[e.to]&&e.cap>e.flow){
p[e.to]=g[x][i];
a[e.to]=min(a[x],e.cap-e.flow);
q.push(e.to);
}
}
if(a[t]) break;
}
if(!a[t]) break;
for(int u=t;u!=s;u=edges[p[u]].from){
edges[p[u]].flow+=a[t];
edges[p[u]^1].flow-=a[t];
}
flow+=a[t];
}
return flow;
}
}EK;