网络流

网络流简介

网络

网络是指一个有向图 \(G=(V,E)\)
每条边 \((u,v)\in E\) 都有一个权值 \(c(u,v)\),称之为容量(Capacity),当 \((u,v)\notin E\) 时有 \(c(u,v)=0\)

\(f(u,v)\) 定义在二元组 \((u\in V,v\in V)\) 上的实数函数且满足
容量限制:对于每条边,流经该边的流量不得超过该边的容量,即 $ f(u,v) \leq c(u,v) $
斜对称性:每条边的流量与其相反边的流量之和为 0,即 \(f(u,v)=-f(v,u)\)
流守恒性:从源点流出的流量等于汇点流入的流量,即 \(\forall x\in V-\{s,t\},\sum_{(u,x)\in E}f(u,x)=\sum_{(x,v)\in E}f(x,v)\)
那么 \(f\) 称为网络 \(G\) 的流函数。对于 \((u,v)\in E,f(u,v)\) 称为边的 流量,\(c(u,v)-f(u,v)\) 称为边的 剩余容量。整个网络的流量为 \(\sum_{(s,v)\in E}f(s,v)\),即 从源点发出的所有流量之和。
一般而言也可以把网络流理解为整个图的流量。而这个流量必满足上述三个性质。
注:流函数的完整定义为

\[f(u,v)=\left\{\begin{aligned} &f(u,v),&(u,v)\in E\\ &-f(v,u),&(v,u)\in E\\ &0,&(u,v)\notin E,(v,u)\notin E \end{aligned}\right. \]

其中有两个特殊的点:源点(Source)\(s\in V\) 和汇点(Sink)\(t\in V,(s\neq t)\)

网络最大流

概述

\(G=(V,E)\) 是一个有源汇点的网络,我们希望在 \(G\) 上指定合适的流 \(f\),以最大化整个网络的流量(即 \(\sum_{(s,v)\in E}f(s,v)\)),这一问题被称作最大流问题(Maximum flow problem)。

dinic

反向边带悔贪心

对每条边建反边,从源开始,只要是边上残量不为 \(0\) ,我们就流过这条边,然后递归到找到终点为止,顺带在边上减去实际流量。

分层图优化保证复杂度

\(bfs\)处理一遍图,处理出图上每个点的深度后再\(dfs\)
\(dfs\)每次递归都只能向下一层递归,避免重复流动浪费资源。

当前弧优化保证复杂度

如果结点 \(u\) 同时具有大量入边和出边,并且 \(u\) 每次接受来自入边的流量时都遍历出边表来决定将流量传递给哪条出边,则 \(u\) 这个局部的时间复杂度最坏可达 \(O(|E|^2)\)。为避免这一缺陷,如果某一时刻我们已经知道边 \((u, v)\) 已经增广到极限(边 \((u, v)\) 已无剩余容量或 \(v\) 的后侧已增广至阻塞),则 \(u\) 的流量没有必要再尝试流向出边 \((u, v)\)。据此,对于每个结点 \(u\),我们维护 \(u\) 的出边表中第一条还有必要尝试的出边。

code
#include<bits/stdc++.h>
#define il inline
#define cs const
#define ri register
#define int long long
using namespace std;

namespace Q{
    il int rd(){
        ri int x=0;ri bool f=0;ri char c=getchar();
        while(!isdigit(c)) f|=(c==45),c=getchar();
        while(isdigit(c)) x=x*10+(c^48),c=getchar();
        return f?-x:x; 
    }
    il void wt(int x){
        if(x<0) x=-x,putchar(45);
        if(x>=10) wt(x/10);
        return putchar(x%10|48),void();
    }
} using namespace Q;

cs int N=205,M=5005,inf=0x3f3f3f3f;

namespace edge{
    struct qwq{
        int v,w,nxt;
    }e[M<<1];
    int h[N];
    il void add(int u,int v,int w,int id){
        e[id]={v,w,h[u]},h[u]=id;
        e[id|1]={u,0,h[v]},h[v]=id|1;
        return;
    }
} using namespace edge;

namespace Dinic{
    int dep[N],now[N];
    il bool bfs(int s,int t){
        memset(dep,0,sizeof(dep));
        queue<int> q; 
        dep[s]=1,q.push(s);
        ri int u,v;
        while(!q.empty()){
            u=q.front(),q.pop(),now[u]=h[u];
            for(ri int i=h[u];i;i=e[i].nxt){
                v=e[i].v;
                if(!dep[v]&&e[i].w){
                    dep[v]=dep[u]+1,q.push(v);
                }
            }
        }
        return dep[t]!=0;
    }
    il int dfs(int u,int res,int t){
        if(u==t) return res;
        ri int tot=res,v,les;
        for(ri int i=now[u];i;i=e[i].nxt){
            v=e[i].v,now[u]=i;
            if(dep[v]==dep[u]+1&&e[i].w){
                les=dfs(v,min(e[i].w,tot),t);
                e[i].w-=les,e[i^1].w+=les,tot-=les;
                if(!tot) break;
            }
        }
        return res-tot;
    }
    il int dinic(int s,int t){
        ri int as=0;
        while(bfs(s,t)){
            as+=dfs(s,inf,t);
        }
        return as;
    }
} using namespace Dinic;

signed main(){
    int n=rd(),m=rd(),s=rd(),t=rd();
    for(ri int i=1,u,v,w;i<=m;++i){
        u=rd(),v=rd(),w=rd(),add(u,v,w,i<<1);
    }
    wt(dinic(s,t));
    return 0;
}

费用流

概述

给定一个网络 \(G=(V,E)\),每条边除了有容量限制 \(c(u,v)\),还有一个单位流量的费用 \(w(u,v)\)
\((u,v)\) 的流量为 \(f(u,v)\) 时,需要花费 \(f(u,v)\times w(u,v)\) 的费用。
\(w\) 也满足斜对称性,即 \(w(u,v)=-w(v,u)\)
则该网络中总花费最小的最大流称为最小费用最大流,即在最大化 \(\sum_{(s,v)\in E}f(s,v)\) 的前提下最小化 \(\sum_{(u,v)\in E}f(u,v)\times w(u,v)\)

SSP

SSP(Successive Shortest Path)算法是一个贪心的算法。它的思路是每次寻找单位费用最小的增广路进行增广,直到图上不存在增广路为止。
如果图上存在单位费用为负的圈,SSP 算法正确无法求出该网络的最小费用最大流。此时需要先使用消圈算法消去图上的负圈。
只需要把\(dinic\)\(bfs\)改成\(spfa\)就可以了

code
#include<bits/stdc++.h>
#define il inline
#define cs const
#define ri register
using namespace std;

namespace Q{
    il int rd(){
        ri int x=0;ri bool f=0;ri char c=getchar();
        while(!isdigit(c)) f|=(c==45),c=getchar();
        while(isdigit(c)) x=x*10+(c^48),c=getchar();
        return f?-x:x; 
    }
    il void wt(int x){
        if(x<0) x=-x,putchar(45);
        if(x>=10) wt(x/10);
        return putchar(x%10|48),void();
    }
} using namespace Q;

cs int N=5e3+1,M=5e4+1,inf=0x3f3f3f3f;

namespace edge{
    int h[N];
    struct qwq{
        int v,w,c,nxt;
    }e[M<<1];
    il void add(int u,int v,int w,int c,int id){
        e[id]={v,w,c,h[u]},h[u]=id;
        e[id|1]={u,0,-c,h[v]},h[v]=id|1;
        return;
    }
} using namespace edge;

namespace Dinic{
    int dis[N];bool vs[N];
    queue<int> q;
    il bool spfa(int s,int t,int n){
        for(ri int i=1;i<=n;++i) dis[i]=inf;
        dis[s]=0,vs[s]=1,q.push(s); ri int u;
        while(!q.empty()){
            u=q.front(),q.pop(),vs[u]=0;
            for(ri int i=h[u];i;i=e[i].nxt){
                if(e[i].w&&dis[e[i].v]>dis[u]+e[i].c){
                    dis[e[i].v]=dis[u]+e[i].c;
                    if(!vs[e[i].v]) vs[e[i].v]=1,q.push(e[i].v);
                }
            }
        }
        return dis[t]^inf;
    }
    il int dfs(int u,int tot,int t,int &cost){
        if(u==t) return tot;
        ri int res=tot,fl;vs[u]=1;
        for(ri int i=h[u];i;i=e[i].nxt){
            if(vs[e[i].v]) continue;
            if(e[i].w&&dis[e[i].v]==dis[u]+e[i].c){
                fl=dfs(e[i].v,min(res,e[i].w),t,cost);
                cost+=fl*e[i].c,e[i].w-=fl,e[i^1].w+=fl;
                if(!(res-=fl)) break;
            }    
        }        
        return vs[u]=0,tot-res;
    }
    il void dinic(int s,int t,int n){
        ri int as=0,fl=0,cost=0;
        while(spfa(s,t,n)){
            while(fl=dfs(s,inf,t,cost)) as+=fl;
        }
        wt(as),putchar(32),wt(cost);
        return;
    }
}using namespace Dinic;

signed main(){
    ri int n=rd(),m=rd(),s=rd(),t=rd();
    for(ri int i=1,u,v,w,c;i<=m;++i){
        u=rd(),v=rd(),w=rd(),c=rd();
        add(u,v,w,c,i<<1);
    }    
    dinic(s,t,n);
    return 0;
}

最小割

概念

对于一个网络流图 \(G=(V,E)\),其割的定义为一种 点的划分方式:将所有的点划分为 \(S\)\(T=V-S\) 两个集合,其中源点 \(s\in S\),汇点 \(t\in T\)

割的容量

我们的定义割 \((S,T)\) 的容量 \(c(S,T)\) 表示所有从 \(S\)\(T\) 的边的容量之和,即 \(c(S,T)=\sum_{u\in S,v\in T}c(u,v)\)。当然我们也可以用 \(c(s,t)\) 表示 \(c(S,T)\)

最小割

最小割就是求得一个割 \((S,T)\) 使得割的容量 \(c(S,T)\) 最小。

最大流最小割定理

定理: \(f(s,t)_{max}=c(s,t)_{min}\)
证明:
对于任意一个可行流 f(s,t) 的割 (S,T),我们可以得到:

\(f(s,t)=S\) 出边的总流量 \(-S\) 入边的总流量 \(\le S\) 出边的总流量 \(=c(s,t)\)

如果我们求出了最大流 \(f\),那么残余网络中一定不存在 \(s\)\(t\) 的增广路经,也就是 \(S\) 的出边一定是满流,\(S\) 的入边一定是零流,于是有:

\(f(s,t)=S\) 出边的总流量 \(-S\) 入边的总流量 \(=S\) 出边的总流量 \(c(s,t)\)

结合前面的不等式,我们可以知道此时 \(f\) 已经达到最大。

code

其实以上都不重要,记住定理最大流等于最小割,跑 \(dinic\) 就可以了

求方案

我们可以通过从源点 \(s\) 开始 \(dfs\),每次走残量大于 \(0\) 的边,找到所有 \(S\) 点集内的点。

il void dfs(int u) {
    vis[u]=1;
    for(ri int i=h[u];i;i=e[i].nxt) {
        if(!vis[e[i].v]&&e[i].w) dfs(e[i].v);
    }
    return;
}

割边数量

如果需要在最小割的前提下最小化割边数量,那么先求出最小割,把没有满流的边容量改成 \(\infty\),满流的边容量改成 \(1\),重新跑一遍最小割就可求出最小割边数量;如果没有最小割的前提,直接把所有边的容量设成 \(1\),求一遍最小割就好了。

问题模型

模型1

问题:
\(n\) 个物品和两个集合 \(A,B\),如果一个物品没有放入 \(A\) 集合会花费 \(a_i\),没有放入 \(B\) 集合会花费 \(b_i\)
还有若干个形如 \(u_i,v_i,w_i\) 限制条件,表示如果 \(u_i\)\(v_i\) 同时不在一个集合会花费 \(w_i\)
每个物品必须且只能属于一个集合,求最小的代价。

做法:
对于每个集合设置源点 \(s\) 和汇点 \(t\)
\(i\) 个点由 \(s\) 连一条容量为 \(a_i\) 的边、向 \(t\) 连一条容量为 b_i 的边。
对于限制条件 \(u,v,w\),我们在 \(u,v\) 之间连容量为 \(w\) 的双向边。

注意到当源点和汇点不相连时,代表这些点都选择了其中一个集合。如果将连向 \(s\)\(t\) 的边割开,表示不放在 \(A\)\(B\) 集合,如果把物品之间的边割开,表示这两个物品不放在同一个集合。

最小割就是最小花费。

模型2

问题:
最大权值闭合图,即给定一张有向图,每个点都有一个权值(可以为正、负或 \(0\)),你需要选择一个权值和最大的子图,使得子图中每个点的后继都在子图中。

做法:
建立超级源点 \(s\) 和超级汇点 \(t\),若节点 \(u\) 权值为正,则 \(s\)\(u\) 连一条有向边,边权即为该点点权;
若节点 \(u\) 权值为负,则由 \(u\)\(t\) 连一条有向边,边权即为该点点权的相反数。
原图上所有边权改为 \(\infty\)
跑网络最大流,将所有正权值之和减去最大流,即为答案。

edit

posted @ 2023-02-10 14:26  雨夜风月  阅读(32)  评论(0编辑  收藏  举报