网络流
文章目录
0.序言
听起来这个名字如此高级,其实不然,大家要抱着一种开放包容的心态去学它。
一.基本概念
先来见一下网络流的基本图形:
可以看到,网络流的图形有一个源点s和一个汇点t,每一条边都有一个容量和流量,流量永远小于或等于容量。
二.预备知识
网络流必须要用数组模拟指针的方法去存图,不能用vector哟。
推荐博起:数据结构之邻接表
三.最大流
1.概念
最大流就是说要求从源点到汇点最大的流量,这个图显而易见,最大流就是15,图中已经标出来了。
那么怎么计算这个最大流呢?
2. EK算法
1.思想
很容易想到一个有些暴力的算法:从源点开始,暴力的找出所有到汇点的路径,再把每条路径上最小的边的容量加起来就行了。
每条能贡献流量的路就叫做增广路。
但是这种方法的正确性有待考究,比如这个图:
假如我们先走s→1→4→t,图就变成了这样:
然后走s→2→4→t,发现4→t的时候路被堵了,但是上一条路径明明可以改成s→1→3→t,这样这一条路就可以顺利走通了,并且找到了最大流,就是20,如下图:
现在最难得问题就是怎么来实现这个算法呢?
现在,引入一种算法:建反边
那么,原图就变成了这个样子(初始化所有反边容量为0):
每走过一条边,就让你走的方向的边的容量减去流量,让反向的边的容量加上流量就行了。最后你找最大流的时候,就直接在图中从s到t走,找出所有增广路(你建的所有的边都可以走),像最初我们的想法那样做就行了。
总结起来,分三步:
1.建反向边
2.找所有增广路
3.统计最大流
这就叫做EK算法。
那为什么要建反边呢?个人认为是为了让路径上遇到容量不允许的边能够改变原来走那条边的路径,让那条路径去找其它可能的边去走,如果找不到,就让这条路径终止,那条路径不变。额。。。大家可以去找找其它的证明康康。
2.实现
先说找增广路:
因为我们要存储这条路径,所以要用pre[x]去存经过点x的路径的前一个点,和点x与前一个点相连的边的编号(要用数组模拟指针去存图哟)。
上代码:
struct node {
int x, edge;
node (){
};
node (int X, int Edge){
edge = Edge;
x = X;
}
}pre[N];
bool find (){
queue <int> Q;
Q.push (s);
memset (vis, 0, sizeof vis);
memset (pre, -1, sizeof pre);
vis[s] = 1;
while (! Q.empty ()){
int f = Q.front ();
Q.pop ();
for (int i = first[f]; i != -1; i = next[i]){
if (! vis[v[i]] && w[i]){
pre[v[i]].x = f;
pre[v[i]].edge = i;
if (v[i] == t)
return 1;
vis[v[i]] = 1;
Q.push (v[i]);
}
}
}
return 0;
}
再来说说统计最大流:
很简单,先沿着找到的增广路找出最小容量的边的容量作为流量,再把路径上的边的容量减去流量,反边加上流量就行了。
void EK (){
while (find ()){
int minn = INF;
for (int i = t; i != s; i = pre[i].x