网络流

最大流问题(Maximum-Flow Problem)

给定带权连通图 G = ( V , E ) G=(V,E) G=(V,E),求源点 s s s汇点 t t t 的最大流。形象的描述:把上图看作一个输油管道网 , s s s 表示发送点, t t t 表示接收点,其他点表示中转站 ,各边的权数表示该段管道的最大输送量,求从 s s s t t t 的最大输油量。
一条边 ( u , v ) (u,v) (u,v)的最大输送量称为容量(capacity),记为 c ( u , v ) c(u,v) c(u,v),边上实际输送的量称为流量(flow),记为 f ( u , v ) f(u,v) f(u,v)
0

最大流问题中容量 c c c 与流量 f f f 满足 3 3 3个性质:
1.容量限制: f ( u , v ) f(u,v) f(u,v) ≤ \le c ( u , v ) c(u,v) c(u,v)

2.斜对称性: f ( u , v ) = − f ( v , u ) f(u,v)= - f(v,u) f(u,v)=f(v,u)
“从 u u u v v v 输送 5 5 5 单位油又从 v v v u u u 输送 3 3 3 单位油” 没有什么意义,它相当于从 u u u v v v 输送 2 2 2 单位油。规定负流 − f ( u , v ) -f(u,v) f(u,v) 表示 从 v v v u u u 输送 f ( u , v ) f(u,v) f(u,v)单位油,即“从 u u u v v v 输送 2 2 2 单位油”等价于“从 v v v u u u 输送 − 2 -2 2 单位油”。

3.流量平衡:除源点 s s s 和汇点 t t t 外的任意结点 u u u 满足 ∑ ( u , v ) ∈ E f ( u , v ) = 0 \sum_{(u,v)\in E}f(u,v)=0\quad (u,v)Ef(u,v)=0
除源点 s s s 和汇点 t t t 任意结点流入量与流出量相等


EK 算法(Edmonds-Karp 算法)

算法思想:从零流开始不断增加可行流
可行流: 增加流量后满足容量限制、斜对称性和流量平衡3个条件

每一条边上的容量与流量之差称为残余容量,简称残量当残量大于0时当前边上才可以增加流量

计算出每一条边上的残量得到的图称为残量网络(residual network)

残量网络中的边包含两种:正向边(正向弧,输入的边)和反向边(反向弧,输入的边反过来,容量为 0 0 0)。当正向弧的流量增加 f f f、残量减少 f f f,同时对应的反向弧的流量增加 − f -f f(减少 f f f)、残量减少 − f -f f(增加 f f f)。这样做的目的是为了给算法一个反悔的机会,反向弧增加流量就相当于正向弧减少流量,而算法本身是一直在增加可行流。

增广路径(augmenting path):残量网络中任何一条从 s s s t t t 的可行路径,路径中所有残量的最小值 f f f大于 0 0 0
把路径上的所有边的流量增加 f f f(对应反向边流量减少 f f f),这个过程称为增广

找出残量网络中的增广路,进行增广,直到残量网络中不存在增广路时得到的网络流为最大网络流。这就是 Edmonds-Karp 算法
E K EK EK算法步骤:
1.构建残量网络
2.找出增广路进行增广(BFS or DFS)
3.无增广路时结束


D i n i c Dinic Dinic 算法

E d m o n d s − K a r p Edmonds-Karp EdmondsKarp 算法 比较简单,足以应对规模小数据不刁钻的网络流问题,但效率上有很大的提升空间。相对的 D i n i c Dinic Dinic 算法 概率也不难,而且速度不错。

D i n i c Dinic Dinic 算法:不停地用 B F S BFS BFS 构建层次图,然后用阻塞流来增广。
层次图: 残量网络图中每一个结点 u u u 到源点 s s s 的距离 d i s ( u ) dis(u) dis(u) 即为点 u u u 所在的 ‘层次’, B F S BFS BFS 在求 d i s dis dis 时,残量网络中残量小于等于 0 0 0 的边视为不存在,残量大于 0 0 0 的边长度(距离)视为 1 1 1
1残量网络去掉与 d i s dis dis 增加方向相反的边以及残量不大于 0 0 0的边,得到层次图。

阻塞流: 不考虑反向弧的 “最大流” ,在层次图上沿着 d i s dis dis 增大的方向进行增广,直到层次图上不存在 s − t s-t st 增广路径时总共增加的流称为阻塞流。
2
三次增广后层次图中不存在 s − t s-t st 增广路径,这三次增广的流合称阻塞流。

算法步骤:
1.构建残量网络
2.构建层次图
3.阻塞流增广
4.无法构建层次图(残量网络上无 s − t s-t st 增广路)时结束

【模板】 题:luogu P3376 模板-网络最大流

#include <iostream>
#include <queue>
#define _for(i,j,k) for(int i=j;i<=k;i++)
#define for_(i,j,k) for(int i=j;i>=k;i--)
using namespace std;
//flow: Dinic algorithm
const int maxn = 1e4+5;
const int maxm = 2e5+5;
const int inf = 1e9; //无限大
struct Edge{ //邻接表边结点 
    int v,cap,flow; //cap:容量;flow:流量;(可以只存残量)
};
Edge e[maxm];
int n,m,s,t,an;
int head[maxn],net[maxm],cnt=1; //邻接表
int vis[maxn],cur[maxn];
void add_edge(int u,int v,int cap,int flow){ //邻接表 存残量网络
    e[++cnt].v=v;
    e[cnt].cap=cap;
    e[cnt].flow=flow;
    net[cnt]=head[u];
    head[u]=cnt;
}
bool bfs(int u){ //bfs构建层次图
    memset(vis,0,sizeof(vis));
    queue<int> q;
    q.push(u);
    vis[u]=1;
    while(!q.empty()){
        u=q.front();
        q.pop();
        for(int i=head[u];i;i=net[i]){
            if(!vis[e[i].v]&&e[i].cap>e[i].flow){ //残量为正时
                vis[e[i].v]=vis[u]+1;
                q.push(e[i].v);
            }
        }
    }
    return vis[t];
}
int dfs(int u,int ca){ //dfs进行阻塞流增广; ca:到点u为止目前最小残量值
    if(u==t||ca==0) return ca; //u为汇点或ca==0终止
    int flow=0,f;
    for(int& i=cur[u];i;i=net[i]){ //不加当前边优化的话:for(int i=head[u];i;i=net[i])
        if(vis[u]+1==vis[e[i].v]&&(f=dfs(e[i].v,min(ca,e[i].cap-e[i].flow)))>0){
            e[i].flow+=f; //更新正向流 
            e[i^1].flow-=f; //更新反向流 编号i和i^1的边互为反向边
            flow+=f;
            ca-=f;
        }
        if(ca==0) break;
    }
    return flow;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n>>m>>s>>t;
    int u,v,cap;
    _for(i,1,m){ //构建残量网络
        cin>>u>>v>>cap;
        add_edge(u,v,cap,0); //正向弧
        add_edge(v,u,0,0);  //反向弧 
    }
    while(bfs(s)){ //构建层次图
        _for(i,1,n) cur[i]=head[i]; //当前边的优化(可以不加)
        an+=dfs(s,inf); //阻塞流增广
    }
    cout<<an;
    return 0;
}

最小费用最大流

在网络流问题上给每一条边增加一个因素:单位流量所需费用(cost),用 a ( u , v ) a(u,v) a(u,v) 表示,求花费最小的最大流。
在网络流最大的前提下求最小费用,而最大流是一定的,唯一有变化的只有在增广路的选择下,而影响费用的会是一条增广路上所有边单位流费用的和,因为一条增广路上每条边上的增广流是相等的。最终的最大流是确定的,所以每次增广时选择增广路上所有边单位流费用和最小的路径增广,直到不存在 s − t s-t st 增广路时结束。算法思想类似与 E K EK EK算法。
将单位流费用类比距离,用 B e l l m a n − F o r d Bellman-Ford BellmanFord 算法找费用和最小的增广路。

【模板】 题:luogu P3381 模板-最小费用最大流

#include <iostream>
#include <queue>
#define LL long long
#define _for(i,j,k) for(int i=j;i<=k;i++)
#define for_(i,j,k) for(int i=j;i>=k;i--)
using namespace std;
const int maxn = 5e3+5;
const int maxm = 5e5+5;
const int inf = 0x3f3f3f3f; //无限大
//min-cost-max-flow
struct Edge{
    int u,v,cap,flow,cost;
};
Edge e[maxm]; 
int n,m,s,t;
int cnt=1,head[maxn],net[maxm]; //邻接表 
int dis[maxn],pre[maxn],vis[maxn],allow[maxn]; //bf

void add_edge(int u,int v,int cap,int flow,int cost){
    e[++cnt].v=v;
    e[cnt].u=u;
    e[cnt].cap=cap;
    e[cnt].flow=flow;
    e[cnt].cost=cost;
    net[cnt]=head[u];
    head[u]=cnt;
}
bool bf(int& flow,int& cost){
    memset(dis,inf,sizeof(dis));
    memset(vis,0,sizeof(vis));
    dis[s]=0;
    allow[s]=inf;
    pre[s]=0;
    queue<int> q;
    q.push(s);
    vis[s]=1;
    int u;
    while(!q.empty()){ //bellman-ford
        u=q.front();
        q.pop();
        vis[u]=0;
        for(int i=head[u];i;i=net[i]){
            if(e[i].cap>e[i].flow&&dis[e[i].v]>dis[u]+e[i].cost){
                dis[e[i].v]=dis[u]+e[i].cost;
                pre[e[i].v]=i;
                allow[e[i].v]=min(allow[u],e[i].cap-e[i].flow);
                if(!vis[e[i].v]){
                    q.push(e[i].v);
                    vis[e[i].v]=1;
                }
            }
        }
    }
    if(dis[t]==inf) return false;
    flow+=allow[t];
    cost+=allow[t]*dis[t];
    u=t;
    while(u!=s){
        e[pre[u]].flow+=allow[t];
        e[pre[u]^1].flow-=allow[t];
        u=e[pre[u]].u;
    }
    return true;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n>>m>>s>>t;
    int u,v,cap,flow,cost;
    _for(i,1,m){
        cin>>u>>v>>cap>>cost;
        add_edge(u,v,cap,0,cost);
        add_edge(v,u,0,0,-cost); //反向弧费用为正向弧费用的相反数
    }
    flow=cost=0;
    while(bf(flow,cost));
    cout<<flow<<" "<<cost;
    return 0;
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值