网络流-最大流(Ford-Fulkerson算法&Dinic算法)

最大流

就如同水流,存在一张图,既有起点又有终点,从起点流向终点的最大流量就是最大流。
在这里插入图片描述
在上面的图假设1为起点,4为终点,那么最大流就为3。
即1 -> 3 ->4和1 -> 2 -> 4两条流的流量之和,而可以知道每条路的流量取决于这条路的短板,即最小的边。

FF算法:

增广路:

如果在计算最大流时,跑过了一条路(比如1 -> 3 -> 4,这条路的流量为2)那么这条路上的所有边都应该减去这条路的流量。这么做显然易见,因为就是减去每条边被占用的流量,通常一条边被减剩下的容量就成为残余流量。

那么增广路就是从起点到终点所有边都不为零的一条路

反边:

FF算法其实就是每次dfs然后找增广路,并把路上的所有边都减去这条路的流量,所有路的流量之和就是最大流。
但是问题来了,有时可能会出现错误增广路,换句话说每次dfs的增广路不一定是最优的。
在这里插入图片描述
比如在上图,如果一开始走1 -> 2 -> 3 ->4那么接下来就没有增广路了。因为3 -> 4这条边被用了, 从而1 -> 3这条边的流没法流向终点。那么此时就需要建立一条反边。即建立一条从3 -> 2的边。

当走增广路时,我们减去正向边的容量,给反向边加上对应的容量。
那么在跑完1 -> 2 -> 3 ->4之后,2 -> 3 的容量为1,而3 -> 2(即反边)的容量为2,那么在下次跑最短路时从1 -> 3之后就可以沿着反路走3 -> 2。然后再走2 -> 4。于是最终仍然得到了正确的最大流。

关于最反边,我个人会有以下几种疑虑

  1. 加了反边会不会出现dfs出无限条增广路?
  2. 加了反边,最大流的实际值会不会被影响?

经过思考之后我觉着可以这么解释:

对于1来说是不可能的,因为流只能从起点流量终点,当起点连接的边的容量被耗尽时,流无论如何都无法流出,所以加了反边之后增广路不可能是无限的。

对于2我是这么想的,既然原本算的不是最优,那么说明最优的路被某条边占用,那么占用的那条路一定用的不是最优的路,即它空出来了一条路,那么此时就可以借助反边去填补空出来的路,实际上就是A走了B的路,然后就给B一个机会去走A的路,由于最终流量是A,B两条路之和,所以最终流量没有改变。

关于反边的表示就很简单了,用偶数表示正边,相邻的奇数表示对应的反边,正边找反边时或者反边找正边时只需要对1进行异或即可

代码:
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 1e5 + 5;
int n,m,cnt = 2,h[maxn];
int st,ed;
bool vis[maxn];
struct node{
    
    
    int w,e,nex;
}e[maxn * 2];
void add(int a,int b,int c){
    
    
    e[cnt].e = b;
    e[cnt].nex = h[a];
    e[cnt].w = c;
    h[a] = cnt++;
}
int dfs(int x,int flow){
    
    //参数为当前节点与当前该增光路的最大流
    vis[x] = 1;
    if(ed == x)return flow;//如果到达终点就返回
    for(int i = h[x]; i ; i = e[i].nex){
    
    
        int endd = e[i].e,w = e[i].w;
        if(!vis[endd] && w){
    
    //不被访问而且该路仍有流就可以走
            int g = dfs(endd, min(flow,w));//走一遍该路,当前流最大值取决于短板
            if(g){
    
    
                e[i].w -= g;//当前流减少g
                e[i ^ 1].w += g;//对应的反边增加g
                return g;
            }
        }
    }
    return 0;
}

int main(){
    
    
    int a,b,c;
    scanf("%d %d %d %d",&n,&m,&st,&ed);
    for(int i = 1; i <= m; i++){
    
    
        scanf("%d %d %d",&a,&b,&c);
        add(a,b,c);//正边
        add(b,a,0);//反边
    }
    int ans = 0;
    while(true){
    
    
        memset(vis,0,sizeof vis);
        int g = dfs(st,INF);
        if(!g)break;//如果流为空就退出
        else ans += g;
    }
    printf("%d",ans);
    return 0;
}
/*
4 5 1 4
1 2 4
1 3 3
2 3 2
2 4 2
3 4 6
*/

Dinic算法:

Dinic算法使用的是分层图对FF算法进行了优化。
对于每一个节点,人为规定它只能流向它的下一层,如果不流通就结束dfs。

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 1e5 + 5;
int n,m,cnt = 2,h[maxn];
int st,ed,dep[maxn];//dep用来存节点的深度
struct node{
    
    
    int w,e,nex;
}e[maxn * 2];
void add(int a,int b,int c){
    
    
    e[cnt].e = b;
    e[cnt].nex = h[a];
    e[cnt].w = c;
    h[a] = cnt++;
}
bool bfs(){
    
    //用于判断当前道路是否流通
    memset(dep,0,sizeof dep);//清空数组
    dep[st] = 1;
    queue<int> qu;
    qu.push(st);
    while(!qu.empty()){
    
    
        int t = qu.front();
        qu.pop();
        for(int i = h[t]; i ; i = e[i].nex){
    
    
            int endd = e[i].e,w = e[i].w;
            if(w && !dep[endd]){
    
    //没被标记,以及仍然有残余流量就继续走
                dep[endd] = dep[t] + 1;
                qu.push(endd);
            }
        }
    }
    if(!dep[ed])return false;//到不达就返回否
    else return true;
}
int dfs(int x,int flow){
    
    //参数为当前节点与当前流向该节点的流
    if(ed == x)return flow;//如果到达终点就返回
    int sum = 0;//表示当前的最大流
    for(int i = h[x]; i ; i = e[i].nex){
    
    
        int endd = e[i].e,w = e[i].w;
        if(dep[x] + 1 == dep[endd] && w){
    
    // 只走下一层
            int g = dfs(endd, min(flow,w));//走一遍该路,当前流最大值取决于短板
            flow -= g,sum += g;//到达x的流量减少g,当前的最大流增加g
            e[i].w -= g;//当前流减少g
            e[i ^ 1].w += g;//对应的反边增加g
        }
        if(!flow)break;//如果当前已经没有流到x的流了就退出
    }
    if(!sum)dep[x] = 0;//如果当前的最大流为0那么说明此节点之后的层不可能流向终点,那么就不要让其他节点的流再经过这个节点了
    return sum;//返回当前最大流
}

int main(){
    
    
    int a,b,c;
    scanf("%d %d %d %d",&n,&m,&st,&ed);
    for(int i = 1; i <= m; i++){
    
    
        scanf("%d %d %d",&a,&b,&c);
        add(a,b,c);//正边
        add(b,a,0);//反边
    }
    int ans = 0;
    while(bfs())ans += dfs(st,INF);
    printf("%d",ans);
    return 0;
}

Dinic + 当前弧优化

其实就是如果说一个点后面的层已经没有任何流量的的贡献了,那么就可以不跑这个点了,所以只需要加一个cur数组记录剩下的边就好了

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 1e5 + 5;
int n,m,cnt = 2,h[maxn];
int st,ed,dep[maxn],cur[maxn];//dep用来存节点的深度
struct node{
    
    
    int w,e,nex;
}e[maxn * 2];
void add(int a,int b,int c){
    
    
    e[cnt].e = b;
    e[cnt].nex = h[a];
    e[cnt].w = c;
    h[a] = cnt++;
}
bool bfs(){
    
    //用于判断当前道路是否流通
    memset(dep,0,sizeof dep);//清空数组
    memcpy(cur,h,sizeof h);//每次都要复制一下h
    dep[st] = 1;
    queue<int> qu;
    qu.push(st);
    while(!qu.empty()){
    
    
        int t = qu.front();
        qu.pop();
        for(int i = h[t]; i ; i = e[i].nex){
    
    
            int endd = e[i].e,w = e[i].w;
            if(w && !dep[endd]){
    
    
                dep[endd] = dep[t] + 1;
                qu.push(endd);
            }
        }
    }
    if(!dep[ed])return false;
    else return true;
}
int dfs(int x,int flow){
    
    
    if(ed == x)return flow;
    int sum = 0;
    for(int i = cur[x]; i ; i = e[i].nex){
    
    
    	cur[x] = i;//记录剩下的边
        int endd = e[i].e,w = e[i].w;
        if(dep[x] + 1 == dep[endd] && w){
    
    
            int g = dfs(endd, min(flow,w));
            flow -= g,sum += g;
            e[i].w -= g;
            e[i ^ 1].w += g;
        }
        if(!flow)break;
    }
    if(!sum)dep[x] = 0;
    return sum;
}

int main(){
    
    
    int a,b,c;
    scanf("%d %d %d %d",&n,&m,&st,&ed);
    for(int i = 1; i <= m; i++){
    
    
        scanf("%d %d %d",&a,&b,&c);
        add(a,b,c);//正边
        add(b,a,0);//反边
    }
    int ans = 0;
    while(bfs())ans += dfs(st,INF);
    printf("%d",ans);
    return 0;
}

洛谷板子题
注意范围,板子没问题的,题中可能爆int

猜你喜欢

转载自blog.csdn.net/qq_36102055/article/details/107326736