Loading

[学习笔记] 网络流最大流

网络流最大流

基础概念和性质

\(f[u,v]\)表示\(u\)\(v\)的流量(流函数),\(c[u,v]\)表示\(u\)\(v\)的流量限制,\(S\)表示源点,\(T\)表示汇点

残量网络:每条边表示每条边剩余的容量

流函数具有一下性质:

  • \(f(x,y)≤c(x,y)\),也就是管道中实际运输的水量不能超过它运输的最大水量。
  • \(f(x,y)=−f(y,x)\)(反对称性)
  • 对于任意 \(x\neq S,x\neq T,\sum\limits_{(u,x)\in E}f(u,x)=\sum\limits_{(x,v)\in E}f(x,v)\),也就是流入多少水就会流出多少水(流量守恒)

而最大流的问题就是让\(\sum\limits_{v}f(s,v)\)最大

Dinic 算法

先回忆一下\(EK\)算法,\(bfs\)遍历整张图直到找到一条增广路,每次只能找一条,速度就比较慢,所以我们就得使用

更好的\(dinic\)算法,\(EK\)一次找一条,但是源点到汇点可能有很多条最短路经,所以我们可以一次性全部扩展

首先我们需要知道分层图:满足\(d[y]=d[x]+1\)的边\((x,y)\)构成的子图被称为分层图

\(d[x]\)表示的就是点\(x\)的层次,也就是\(S\)\(x\)最到需要经过的边数(和流量没有关系)

至于为什么要分层,问就是防止\(dfs\)乱走

算法流程

先进行一边\(bfs\)求出源点到每个点的最短边\(d_x\),然后将图分层,每次只考虑 \(d_y=d_x+1\) 的边\((x,y)\)

每次\(dfs\),从\(S\)开始携带\(∞\)的流往下递归,递归时枚举当前节点的所有相邻的点,并尽可能将有的流发给相邻的点

并继续递归,递归结束后更新现有的流,如果没有流或者所有相邻的点都访问完了就直接返回,如果到了汇点\(T\)

就把所有的现有的流都送出去

简单来说就是

  1. \(bfs\)求出每个点的\(d_x\)给每个点一个层次,得到一张分层图
  2. 根据层次反复\(dfs\),每次尝试给予时,只考虑给予自己下一层的点,就可以防止混乱

无用点剪枝

在一轮\(dfs\)中,如果我们走到一个点,它能流的流量都流完了(也就是返回值为\(0\))那下一次再遇到这个点就没有

必要再次遍历了

当前弧优化

从前面的介绍可以知道,这个\(dinic\)分明就是\(dfs\),既然是\(dfs\)没有剪枝,那它就是指数级暴力(

对于一个节点\(x\),由于有效边构成的字图一定是\(DAG\)(有向无环图),所以走到第\(i\)条弧时,前\(i-1\)条弧到汇点

的流都一定流满了,没有可行的路线了,也就是说每处理完一条从\(x\)出发的弧,以后就不用在访问了

于是我们考虑开一个数组\(now_x\)表示当前考虑到了哪一条弧(不是某个点),使用方法和\(head\)是相同的,这样就

可以改变每次搜索的起点了,由于大前提是在分层图中,所以每次\(bfs\) 就把\(now_x\)设为邻接表的表头

\(Q\And A\)

\(Q:\)为什么当前弧优化不写成\(now[u]=nxt[i]\),难道不会重复遍历一条边吗?

\(A:\)除非当前这条边到源点了或者该流的都流完了,否则都有多流一些的可能,而且从名字“当前”也能看出吧(

\(Q:\)为什么要建反向边?

\(A:\)

Im9qc4.png

注:我们这里指的边是指构成残量网络的边

如果我们不加反向边,那么找增广路就是乱走,但这肯定是不行的,所以我们必须要有一个反悔机制

现在我们来讲一下找增广路遇到反边的清况:

假设我们现在找到了一条增广路\(3\to2\to 4\to 5\) 并成功的流过去\(a\)的流量,然后反向边就会增加\(a\)

那假设我们又找到了一条增广路\(6\to4\to2\to1\),并成功的流过去\(b\)的流量,两条增广路都经过了\(2,4\),这条

边就会\(-b\)反向边的反向边(也就是原来的边)就会\(+b\),是不是就等于向第一条增广路返还了流量?

但是现在可能又会有一个问题:\(4\to2\)这条边本来是不存在的啊!它是我们加的反向边,那流量是怎么过去的?

整体地看,这样做是不是相当于\(6\to4\to5,3\to2\to1\)这两个路经流了\(b\)的流量?

\(Q:\)如果没有到源点会返回什么?

\(A:\)看起来会返回\(ret\)但是在没有到源点的情况下,其实\(ret\)一直都会等于\(0\)

\(Q:\) 为什么一开始从\(S\)开始携带\(∞\)的流往下递归?实际的流量万一不是容量呢?

\(A:\)因为我们是回溯的时候更新的,所以如果能到源点,它更新后一定是符合要求的,而且往大了去准没错(

​ 注意到我们求的是 \(dinic(s,inf)\) 也就是从源点出去的流

\(Q:\)还是不懂怎么办?

\(A:\)感性理解就好了

code

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <map>
#include <queue>
#define int long long 
using namespace std;
const int inf=1e18;
const int N=2e6+10;
int n,m,s,t,head[N],nxt[N],val[N],tot=1,d[N],now[N],ver[N];
//注意tot的初始值要设为1,因为1^1=0
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return x*f;
}
inline void add(int x,int y,int z){
    ver[++tot]=y,nxt[tot]=head[x],val[tot]=z,head[x]=tot;
}
inline bool bfs(){
    for(int i=1;i<=n;i++) d[i]=inf,now[i]=head[i];
    queue<int> q;
    q.push(s);d[s]=0;
    while(!q.empty()){
	int u=q.front();q.pop();
	for(int i=head[u];i;i=nxt[i]){
	    int v=ver[i];
	    if(val[i]&&d[v]==inf){//满足增广路的性质且没有被访问过
		q.push(v);
		d[v]=d[u]+1;//分层
		if(v==t) return 1;//找到增广路
	    }
	}
    }
    return 0;
}
inline int dinic(int u,int sum){//求单次增广送出去的流量,sum
    if(u==t||!sum) return sum;//到汇点,直接把全部送出去
    int ret=0;//目前送出去多少流
    for(int i=now[u];i;i=nxt[i]){
	now[u]=i;//当前弧优化
	int v=ver[i];
	if(val[i]&&d[v]==d[u]+1){//只考虑下一层的点且满足增广路的性质
	    int k=dinic(v,min(sum-ret,val[i]));//k是v送出去的流
	    if(!k) d[v]=inf;//无用点剪枝
	    val[i]-=k;val[i^1]+=k;//递归更新现有的流
	    ret+=k;
	    if(ret==sum) return ret;//如果全部送完就直接返回
	}
    }
    return ret;
}
signed main(){
    n=read();m=read();s=read();t=read();
    for(int i=1;i<=n;i++){
	int u=read(),v=read(),w=read();
	add(u,v,w);
	add(v,u,0);
    }
    int res=0;
    while(bfs()) res+=dinic(s,inf);//流量守恒
    cout<<res<<endl;
    return 0;
}

最小割

概念

  • 网络的割集:把一个源点为 \(S\),汇点为 \(T\) 的网络中的所有点划分成两个点集 \(A\)\(B\)\(A∩B=∅\)

    \(A∪B=V\),$ S∈A,T∈B $ 由 \(x\in A\) 连向 \(y∈ B\) 的边的集合称为割集

  • 最小割:在一个网络中,使得边容量之和最小的割集(这里的边指的是正向边也就是从\(A\to B\) 的边)

  • 最大流最小割定理:在任何网络中,最大流的值等于最小割的容量(注意是流量等于容量)

证明

  • ①最大流 \(\leq\) 最小割

    首先根据割的定义,所有的流都必然经过割边集中的某一条边,那么流量总和最大就是割边集总和。

  • ②最大流 \(\geq\) 最小割

    考虑我们求出了一个最大流,那么某些边会成为瓶颈,即残量网络上为 \(0\).

    这些边一定分布成为一个割,否则仍然会有增广路。

解释一下第一条:割边集的总和为容量和,由于的流都必然经过割边集中的某一条边,所以最大流的流量就是割

边的流量和,由 \(f(x,y)≤c(x,y)\) 可知流量总和肯定小于等于容量总和

解释一下第二条:假设这些边的分布不一定成为一个割且不会出现增广路,根据割集的定“由 \(x\in A\) 连向 \(y∈ B\)

的边的集合称为割集”,则肯定会出现一条路,这条路上的流量都大于 \(0\)(也就是增广路),然后增广路上就会出

现流量为 \(0\) 的边,把这条增广路“割”掉,这与之前的假设矛盾,但知道这个有什么用呢?能说明②最大流 \(\geq\) 最小

割吗?答案肯定是可以,我们不难看出,这里的割不是严格意义上的割,我们可以假设有一条增广路,路上所有

点的权值都一样,那增广完所有点的流量就变成 \(0\) 了,又因为这些边一定分布成为一个割,所以②最大流 \(\geq\) 最小

割,都为 \(0\) 了,说明至少为容量吧,而且它的边数大于等于真正的割

最小割的方案

跑完最大流之后,从原点选取非 \(0\)BFS,把这些点成为 \(S\)

一端在 \(S\) 集一端不在的边即为最小割

割边数量

只需要将每条边的容量变为 \(1\),然后重新跑 \(\text{Dinic}\) 即可

dinic的一些证明

为什么到这里才讲,因为我之前没看懂(

  1. 为什么一直不断地增广,直到不能增广为止得到的流就是最大流?

\(A,B\)\(G\) 的一种割

先记 \(P=\sum\limits_{u\in A,v\in B}f(u,v)−\sum\limits_{u\in A,v\in B}f(v,u)\),那么对一个流都有它的流量等于 \(P\)

考虑对 \(G\) 进行增广,假设其有一条增广路经过 \(v_1=S,v_2,…,v_k=T\)\(k\) 个点,增加了 \(f\) 的流量。对于 \(v_i∈A,v_i+1∈B\),边 \((v_i,v_i+1)\) 应属于这种割的正向割边,对 \(P\) 产生 \(+f\) 的贡献,而对于

\(v_i∈B,v_i+1∈A\),边 \((v_i,v_i+1)\) 应属于这种割的负向割边,对 \(P\) 产生 \(−f\) 的贡献。并且由于最终 \(S∈A\)

\(T∈B\),故 \(+f\) 贡献次数应当比 \(−f\) 贡献次数多 \(1\)(类似匈牙利的增广路,有来有回),故一次增广会对 \(P\) 产生

\(f\) 的贡献。而这个流的流量就是把每次增广增加的流量 \(f\) 加起来。故原命题得证。

  1. 为什么”不能增广为止“这个状态时网络的流量达到最大值

\(A\) 为增广到当前状态为止,剩余流量为 \(S\) 能到达的点,\(B=V−A\),显然 \(A,B\) 组成了 \(G\) 的一个割。

根据之前的推论,此时网络中的流量应当等于该割中正向割边与反向割边流量的差。

而显然正向割边的流量就等于其容量,负向割边的流量为 \(0\)。(也说明了最小割定理)

故此时流量 = 此割中正向边的容量之和。

所以此时流量达到最大值。

学习对象(

详细的博客——ET2006

易懂——摸鱼酱

对反边的解释比较好——Eleven谦

丰富——辰星凌

日报

command_block的博客

posted @ 2021-11-03 22:35  Miraii  阅读(179)  评论(0)    收藏  举报