首页 > 其他分享 >网络流学习笔记

网络流学习笔记

时间:2024-08-13 11:39:22浏览次数:10  
标签:容量 增广 int flow 网络 流量 学习 笔记

前言

zr游记系列因作者在考试的重重打击下,它,寄了。

作者还是写下了这一片“网络流学习笔记”来纪念学会了网络流。

废话不多说了,笔记要不是抄别人博客的,要么是抄老师课件的。

基本概念

关于网络流的

  • 网络流 \((Net Work Flow)\): 一种类比水流的解决问题的方法。(下述概念均会用水流进行解释)

  • 网络 \((NetWork)\) : 可以理解为拥有源点汇点的有向图。(运输水流的水管路线路)

  • 弧 \((arc)\): 可以理解为有向边。下文均用 “” 表示。(水管)

  • 弧的流量 \((Flow)\) : 简称流量。在一个流量网络中每条边都会有一个流量,表示为 \(f(x,y)\) ,根据流函数 \(f\) 的定义,\(f(x,y)\) 可为负。(运输的水流量)

  • 弧的容量 \((Capacity)\): 简称容量。在一个容量网络中每条边都会有一个容量,表示为 \(c(x,y)\)。(水管规格。即可承受的最大水流量)

  • 源点 \((Sources)\) : 可以理解为起点。它会源源不断地放出流量,表示为 \(S\)。(可无限出水的 \(NB\) 水厂)

  • 汇点 \((Sinks)\): 可以理解为终点。它会无限地接受流量,表示为 \(T\)。(可无限收集水的 \(NB\) 小区)

  • 容量网络: 拥有源点汇点且每条边都给出了容量网络。(安排好了水厂,小区和水管规格的路线图)

  • 流量网络: 拥有源点汇点且每条边都给出了流量网络。(分配好了各个水管水流量的路线图)

  • 弧的残留容量: 简称残留容量。在一个残量网络中每条边都会有一个残留容量 。对于每条边,残留容量$ =$容量 \(−\)流量。初始的残量网络即为容量网络。(表示水管分配了水流量后还能继续承受的水流量)

  • 残量网络: 拥有源点汇点且每条边都有残留容量网络残量网络 \(=\) 容量网络 \(−\) 流量网络。(表示了分配了一定的水流量后还能继续承受的水流量路线图)

关于流量容量残留容量的理解见下图:
(用 \(c\) 表示容量,\(f\) 表示流量,\(flow\) 表示残留容量

三大性质

  • 容量限制:\(\forall (x,y)\in E,f(x,y)\le c(x,y)\)

(如果水流量超过了水管规格就爆了呀)

  • 流量守恒:\(\forall (x,y)\in V且x\ne S且x\ne T, {\textstyle \sum_{(u,x)\in E}^{}} f(u,x)={\textstyle \sum_{(x,v)\in E}^{}} f(x,v)\)

(对于所有的水管交界处,有多少水流量过来,就应有多少水流量出去,保证水管质量良好不会泄露并且不会无中生有)

  • 斜对称性:\(\forall (x,y)\in E,f(y,x)=-f(x,y)\)

(可以暂且感性理解为矢量的正负。在网络流问题中,这是非常重要的一个性质)

最大流

概念补充

  • 网络的流量: 在某种方案下形成的流量网络汇点接收到的流量值。(小区最终接收到的总水流量)

  • 最大流网络流量最大值。(小区最多可接受到的水流量)

  • 最大流网络: 达到最大流流量网络。(使得小区接收到最多水流量的分配方案路线图)

增广路算法(\(EK\))

概念补充

  • 增广路 \((Augmenting Path)\): 一条在残量网络中从 \(S\) 到 \(T\) 的路径,路径上所有边的残留容量都为正。(可以成功从水厂将水送到小区的一条路线)

  • 增广路定理 \((Augmenting Path Theorem)\)流量网络达到最大流当且仅当残量网络中没有增广路。(无法再找到一路线使得小区获得更多的流量了)

  • 增广路方法 \((Ford−Fulkerson)\): 不断地在残量网络中找出一条从 \(S\) 到 \(T\) 的增广路,然后根据木桶定律汇点发送流量并修改路径上的所有边的残留容量,直到无法找到增广路为止。该方法的基础为增广路定理,简称 \(FF\) 方法。(如果有一条路径可以将水运到小区里就执行,直到无法再运送时终止)

  • 增广路算法 \((Edmonds−Karp)\): 基于增广路方法的一种算法,核心为 \(bfs\) 找最短增广路,并按照 \(FF\) 方法执行操作。增广路算法的出现使得最大流问题被成功解决,简称 \(EK\) 算法。

算法流程

下面对 \(EK\) 算法作详细介绍。

\(1\) . 用 \(bfs\) 找到任意一条经过边数最少的最短增广路,并记录路径上各边残留容量的最小值 \(cyf\)(残 \(c\) 余 \(y\) \(flow\))。 (木桶定律。众多水管一个也不能爆,如果让最小的刚好不会爆,其它的也就安全了)

\(2\) . 根据 \(cyf\) 更新路径上边及其反向边的残留容量值。答案(最大流)加上 \(cyf\)。

\(3\) . 重复 \(1,2\) 直至找不到增广路为止。

对于 \(2.\) 中的更新操作,利用链表的特性,从 \(2\) 开始存储,那么 \(3\) 与 \(2\) 就互为一对反向边,\(5\) 与 \(4\) 也互为一对反向边 \(\dots\).

只需要记录增广路上的每一条边在链表中的位置下标,然后取出来之后用下标对 \(1\) 取异或就能快速得到它的反向边。

理解

关于建图

在具体实现中,由于增广路是在残量网络中跑的,所以只需要用一个变量 \(flow\) 记录残留容量就足够了,容量流量一般不记录。

为了保证算法的最优性(即网络的流量要最大),可能在为某一条边分配了流量后需要反悔,所以要建反向边。在原图中,正向边的残留容量初始化为容量,反向边的残留容量初始化为 \(0\)(可理解为反向边容量为 \(0\) )。

当我们将边 \((x,y)\)(在原图中可能为正向也可能为反向)的残留容量 \(flow\) 用去了 \(F\) 时,其流量增加了 \(F\),残留容量 \(flow\) 应减少 \(F\)。根据斜对称性,它的反边 \((y,x)\)
流量增加了 \(−F\),残留容量 \(flow′\) 应减去 \(−F\)(即加上 \(F\))。

那么如果在以后找增广路时选择了这一条边,就等价于:将之前流出去的流量的一部分(或者全部)反悔掉了个头,跟随着新的路径流向了其它地方,而新的路径上在到达这条边之前所积蓄的流量 以及 之前掉头掉剩下的流量 则顺着之前的路径流了下去。

同理,当使用了反向边 \((y,x)\) 的残留容量时也应是一样的操作。

还是之前那个图,下面是找到了一条最短增广路 \(1→3→2→4\)(其中三条边均为黑边)后的情况:(不再显示容量流量,用 \(flow\) 表示残留容量,灰色边表示原图上的反向边,蓝色小水滴表示水流量)

然后是第二条最短增广路 \(1→7→6→2⇢3→8→5→4\)(其中 \(f(2,3)\) 为灰边,其余均为黑边,紫色小水滴表示第二次新增的水流量):

注:由于在大部分题目中都不会直接使用容量和流量,所以通常会直接说某某之间连一条流量为某某的边,在没有特别说明的情况下,其要表示的含义就是残留容量。后面亦不再强调“残留”,直接使用“流量”。

复杂度

\(O(nm^{2})\)

code

#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
#define Re register int
using namespace std;
const int N=1e4+3,M=1e5+3,inf=2e9;
int x,y,z,o=1,n,m,h,t,st,ed,maxflow,Q[N],cyf[N],pan[N],pre[N],head[N];
struct QAQ{int to,next,flow;}a[M<<1];
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
inline void add(Re x,Re y,Re z){a[++o].flow=z,a[o].to=y,a[o].next=head[x],head[x]=o;}
inline int bfs(Re st,Re ed){
    for(Re i=0;i<=n;++i)pan[i]=0;
    h=1,t=0,pan[st]=1,Q[++t]=st,cyf[st]=inf;//注意起点cfy的初始化
    while(h<=t){
        Re x=Q[h++];
        for(Re i=head[x],to;i;i=a[i].next)
            if(a[i].flow&&!pan[to=a[i].to]){//增广路上的每条边残留容量均为正
            	cyf[to]=min(cyf[x],a[i].flow);
            	//用cyf[x]表示找到的路径上从S到x途径边残留容量最小值
            	Q[++t]=to,pre[to]=i,pan[to]=1;//记录选择的边在链表中的下标
            	if(to==ed)return 1;//如果达到终点,说明最短增广路已找到,结束bfs
            }
    }
    return 0;
}
inline void EK(Re st,Re ed){
    while(bfs(st,ed)==1){
        Re x=ed;maxflow+=cyf[ed];//cyf[ed]即为当前路径上边残留容量最小值
        while(x!=st){//从终点开始一直更新到起点
            Re i=pre[x];
            a[i].flow-=cyf[ed];
            a[i^1].flow+=cyf[ed];
            x=a[i^1].to;//链表特性,反向边指向的地方就是当前位置的父亲
        }
    }
}
int main(){
    in(n),in(m),in(st),in(ed);
    while(m--)in(x),in(y),in(z),add(x,y,z),add(y,x,0);
    EK(st,ed);
    printf("%d",maxflow);
}


\(Dinic\)

在 \(EK\) 算法中,每一次 \(bfs\) 最坏可能会遍历整个残量网络,但都只会找出一条最短增广路

那么如果一次 \(bfs\) 能够找到多条最短增广路,速度就上去了。

\(Dinic\) 算法便提供了该思路的一种实现方法。

网络流的算法多且杂,对于初学者来说,在保证效率的前提下优化 \(Dinic\) 应该是最好写的一种了。

算法流程

\(1\) . 根据 \(bfs\) 的特性,找到 \(S\) 到每个点的最短路径(经过最少的边的路径),根据路径长度对残量网络进行分层,给每个节点都给予一个层次,得到一张分层图

\(2\) . 根据层次反复 \(dfs\) 遍历残量网络,一次 \(dfs\) 找到一条增广路并更新,直至跑完能以当前层次到达 \(T\) 的所有路径。

多路增广

可以发现,一次 \(bfs\) 会找到 \([1,m]\) 条增广路,大大减少了 \(bfs\) 次数,但 \(dfs\) 更新路径上的信息仍是在一条一条地进行,效率相较于 \(EK\) 并没有多大变化。

为了做到真正地多路增广,还需要进行优化。

在 \(dfs\) 时对于每一个点 \(x\),记录一下 \(x⇝T\) 的路径上往后走已经用掉的流量,如果已经达到可用的上限则不再遍历 \(x\) 的其他边,返回在 \(x\) 这里往后所用掉的流量,回溯更新 \(S⇝x\) 上的信息。

如果到达汇点则返回收到的流量,回溯更新 \(S⇝T\) 上的信息。

弧优化

原理:

在一个分层图当中,\(\forall x\in V\),任意一条从 \(x\) 出发处理结束的边(弧),都成了 “废边”,在下一次到达 \(x\) 时不会再次使用。(水管空间已经被榨干净了,无法再通过更多的水流,直接跳过对这些边的无用遍历)

实现方法:

用数组 ¥cur_{x}$ 表示上一次处理 \(x\) 时遍历的最后一条边(即 \(x\) 的当前弧),其使用方法与链表中的 \(head\) 相同,只是 \(cur\) 会随着图的遍历不断更新。由于大前提是在一个分层图当中,所以每一次 \(bfs\) 分层后都要将 \(cur\) 初始化成 \(head\) 。

特别的,在稠密图中最能体现当前弧优化的强大。

复杂度

\(O(n^{2}m)\)

code

bool bfs(){
	memset(d,0,sizeof(d));
	queue<int >q;
	q.push(1);
	d[1]=1;
	while(q.size()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=ad[i].nxt){
			int v=ad[i].v,w=ad[i].w;
			if(d[v]||!w){
				continue;
			}
			q.push(v);
			d[v]=d[u]+1;
			if(v==f+n+n+dr+2)return 1;
		}
	}
	return 0;
}
int dfs(int u,int flow){
	if(u==n+n+f+dr+2)return flow;
	int rest=flow,tot=0;
	for(int i=head[u];i;i=ad[i].nxt){
		int v=ad[i].v,w=ad[i].w;
		if((!w)||d[u]+1!=d[v])continue;
		int k=dfs(v,min(w,rest));
		rest-=k;
		tot+=k;
		ad[i].w-=k;
		ad[i^1].w+=k;
	}
	return tot;
}
void dinic(){
	while(bfs())max_f+=dfs(1,0x7fffffff);
}

一道例题

[USACO07OPEN] Dining G

网络最大瘤解决二分图匹配的连边思路:

\(s→left→right→t\)

这道题貌似是个“三分图”最大“匹配”(注意这里加了引号)。

可以将食物、牛、饮料分别定义为:左部点,中部点,右部点。

但是这里的“匹配”就不是原来的匹配了。

匹配本来的意思是:任意两边不共端点

而在这里,“匹配”的意思任意两条从左部到右部的路径没有公共

但是建图的方式还是可以仿照二分图最大匹配的——

\(s→left→mid→right→t\)

但是这样一来,如果您真正理解了网络最大瘤做二分图最大匹配的原理时,你就会发现:

本来经过所有点的流量都应该小于等于1,但是在这里经过mid点的流量可能会大于1!这样一来,就会出现一个问题:有两条路径共mid点,与我们之前谈到的“三分图”最大“匹配”的定义不符。

这说明我们需要对mid点进行“限流”(没错,一开始我就是把这个东西称为“限流”,后面才知道叫拆点)

限流的方法很简单:在《挑战程序设计竞赛——第二版》的第3章第5节,214页第二点有提到:

将一个点拆做入点出点,连一条权值为限制流量的边。

所以就可以这样建图:

\(s→left→mid→mid →right→t\)

然后跑 \(Dinic\) 即可(虽说看起来 \(EK\) 可过)。

最小割

1.概念

  • 网络的割集\((Network Cut Set)\) : 把一个源点为 \(S\),汇点为 \(T\) 的网络中的所有点划分成两个点集 \(s\) 和 \(t\),\(S\in s,T\in t\),由 \(x\in s\) 连向 \(y\in t\) 的边的集合称为割集。可简单理解为:对于一个源点为 \(S\),汇点为 \(T\) 的网络,若删除一个边集 \(E′\subseteq E\) 后可以使 \(S\) 与 \(T\) 不连通,则成 \(E′\) 为该网络的一个割集。(有坏人不想让小区通水,用锯子割掉了一些边)

  • 最小割 \((Minimum Cut)\) : 在一个网络中,使得边容量之和最小的割集。(水管越大越难割,坏人想要最节省力气的方案)

  • 最大流最小割定理:$(Maximum Flow Minimum Cut Theorem) $:任意一个网络中的最大流等于最小割

费用流

概念

每条边在容量 \(c\) 的基础上还有费用 \(w\),表示在这条边每流单位流量需要支付 \(w\) 的代价,由此可以定义最小费用最大流。

\(EK\)

只需将最大流 \(EK\) 算法中的流程 \(1\) “ \(bfs\) 找到任意一条最短增广路 ” 改为 “ \(Spfa\) 找到任意一条单位费用之和最小增广路 ”,即可得到最小费用最大流

特别的,为了提供反悔机会,原图中 \(\forall (x,y)\in E\) 的反向边单位费用应为 \(−w(x,y)\) 。为什么不用 \(dijkstra\)?原因就在这里啊!)

#include<algorithm>
#include<cstdio>
#include<queue>
#define LL long long
#define Re register int
using namespace std;
const int N=5003,M=5e4+3,inf=2e9;
int x,y,z,w,o=1,n,m,h,t,st,ed,cyf[N],pan[N],pre[N],dis[N],head[N];LL mincost,maxflow; 
struct QAQ{int w,to,next,flow;}a[M<<1];queue<int>Q;
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
inline void add(Re x,Re y,Re z,Re w){a[++o].flow=z,a[o].w=w,a[o].to=y,a[o].next=head[x],head[x]=o;}
inline void add_(Re a,Re b,Re flow,Re w){add(a,b,flow,w),add(b,a,0,-w);}
inline int SPFA(Re st,Re ed){
    for(Re i=0;i<=ed;++i)dis[i]=inf,pan[i]=0;
    Q.push(st),pan[st]=1,dis[st]=0,cyf[st]=inf;
    while(!Q.empty()){
    	Re x=Q.front();Q.pop();pan[x]=0;
    	for(Re i=head[x],to;i;i=a[i].next)
            if(a[i].flow&&dis[to=a[i].to]>dis[x]+a[i].w){
                dis[to]=dis[x]+a[i].w,pre[to]=i;
                cyf[to]=min(cyf[x],a[i].flow);
                if(!pan[to])pan[to]=1,Q.push(to);
            }
    }
    return dis[ed]!=inf;
}
inline void EK(Re st,Re ed){
    while(SPFA(st,ed)){
    	Re x=ed;maxflow+=cyf[ed],mincost+=(LL)cyf[ed]*dis[ed];
    	while(x!=st){//和最大流一样的更新
            Re i=pre[x];
            a[i].flow-=cyf[ed];
            a[i^1].flow+=cyf[ed];
            x=a[i^1].to;
    	}
    }
}
int main(){
    in(n),in(m),in(st),in(ed);
    while(m--)in(x),in(y),in(z),in(w),add_(x,y,z,w);
    EK(st,ed);
    printf("%lld %lld",maxflow,mincost);
}

标签:容量,增广,int,flow,网络,流量,学习,笔记
From: https://www.cnblogs.com/AUBSwords/p/18356552

相关文章

  • 【笔记】传统势能线段树
    1引入传统线段树能够通过打标记实现区间修改的条件有两个:能够快速处理标记对区间询问结果的影响;能够快速实现标记的合并。有的区间修改不满足上面两个条件。但存在一些奇妙的性质,使得序列每个元素被修改的次数有一个上限。如果我们保证每暴力\(O(\logn)\)修改一次的时......
  • 控制SD图片生成的神经网络模型--ControlNet
    ControlNet是一个通过添加额外条件来控制SD中图像生成的神经网络,可以使用ControlNet来做以下事情:指定人体姿势。从另一幅图像复制图片的构图。生成参考图片类似的图像。将涂鸦图片变成专业的图像。ControlNet是用于控制SD的神经网络模型。您可以将ControlNet......
  • 图数据库在社交网络分析中的应用
    图数据库在社交网络分析中的应用随着互联网的飞速发展,社交网络已成为人们日常生活中不可或缺的一部分。这些平台不仅连接了数以亿计的用户,还生成了海量的、高度互连的数据。如何有效地处理和分析这些数据,以理解用户行为、优化用户体验、提升平台价值,成为了一个重要的研究课题......
  • 大语言模型从零开始训练全面指南:预训练、Tokenizer训练、指令微调、奖励模型、强化学
    在这篇文章中,我们将尽可能详细地梳理一个完整的LLM训练流程。包括模型预训练(Pretrain)、Tokenizer训练、指令微调(InstructionTuning)、奖励模型(RewardModel)和强化学习(RLHF)等环节。1.预训练阶段(PretrainingStage)工欲善其事,必先利其器。当前,不少工作选择在一个较......
  • HCIP笔记8-BGP(2)
    一、BGP的宣告问题1.在BGP协议中每台运行BGP的设备上,宣告本地直连路由2.*在BGP协议中运行BGP协议的设备还可以宣告通过IGP学习到的,未运行BGP协议设备产生的路由;在BGP协议中宣告本地路由表中路由条目时,将携带本地到达这些目标的IGP度量值;传递到BGP邻居处;其他AS设备便于选择离......
  • KVM网络模式
    在KVM(Kernel-basedVirtualMachine)虚拟化环境中,有几种不同的网络模式可以用来配置虚拟机(VMs)的网络连接。这些模式主要通过libvirt工具来设置,libvirt是一个管理KVM和其他虚拟化技术的工具集。下面是KVM中常用的几种网络模式:Bridge(桥接)模式:描述:在这种模式下,虚拟机与宿......
  • 极限学习笔记
    这个人太菜了,轻喷。数列极限定义数列的概念自变量为正整数的函数\(u_n=f(n)\),其中\(n=1,2,3\cdots\),将其函数值按自变量从小到大排成一列数\(u_1,u_2\cdotsu_n\cdots\),称为数列,将其简记为\(\{u_n\}\)。其中\(u_n\)称为数列的通项或者一般项。、数列极限的定义(\(\eps......
  • 【学习笔记6】论文SQLfuse: Enhancing Text-to-SQL Performance through Comprehensiv
    Abstract        Text-to-SQL转换是一项关键创新,简化了从复杂SQL语句到直观自然语言查询的转换,尤其在SQL在各类岗位中广泛应用的情况下,这一创新显得尤为重要。随着GPT-3.5和GPT-4等大型语言模型(LLMs)的兴起,这一领域得到了极大的推动,提供了更好的自然语言理解......
  • 提升效率的印象笔记(Evernote)使用指南
    印象笔记(Evernote)是一个功能强大、跨平台的笔记管理工具,它不仅能帮助你记录日常笔记,还可以用于整理工作计划、管理项目、存储灵感和信息等。为了最大化地提高你的生产力,以下将介绍一些高效使用印象笔记的技巧,帮助你充分发挥其潜力。一、入门基础:理解印象笔记的基本概念1.1笔......
  • Java网络编程——Cookie & Session
    cookie前面我们学习Okhttp3库可以调用API、抓取网页、下载文件。但是这些操作都是不要求登录的,如果API、网页、文件等内容要求登录才能访问,就需要学习新的cookie相关的知识了。下面以豆瓣为例,使用Java程序读取“我的豆瓣”页面内容,在此过程中熟悉运用cookie。所......