当前位置: 首页 > news >正文

网络流初步

网络流初步

文章目录

  • 网络流初步
    • 概念介绍
    • 最大流
    • 费用流

概念介绍

网络流不同之处在于它的本质图论,但是把图论的某些概念换了一个说法而已,初步只要了解网络流的各个概念就可以明白的很快。

下述概念是本人自己定义的,对于网络流的题目做的还不多,后续会更正。

首先需要理解:网络流其实是在一个有向图中,从某个原点开始放水,水沿着管道最后流向汇点的过程,基于此就可以很好的理解一些概念和定理,而不需要十分严谨的证明(因为证明意义不大

流网络: 对于一个 G(V,E)G(V,E)G(V,E) 的单向图,我们称为流网络

容量C: 点和点之间的边权成为容量

流量F: 通俗点说,点与点之间流过的水流量,和物理定义流量一致。一个很显然的性质:F≤CF\le CFC 即流量不能超过容量

原点和汇点: 水流出的点,和水流入的点。

可行流: 全称可以流进去的流量,F(u,v)F(u,v)F(u,v) 表示从 u 点流到 v 点的流量

流网络的可行流:即从原点或者汇点流入或者流出的总流量 ∣F∣|F|F

最大流: 全称最大可行流,比较抽象,实际意义就是:从原点可以流出的最大流量,或者是汇集到汇点的最大流量。

残留网络: 对于一个每条边都确定流量F的流网络,如果存在某条边 C−F>0C-F>0CF>0 ,即如果可以的话,还可以从 u→vu\to vuv 流动 C−FC-FCF 的水,那么我们将这些差值重新构造一张图,形成的网络被称为残留网络,十分形象,同时还会建立反向边,不过权值变成 −F-FF ,可以理解为水反向流动。此时构成完整的残存网络。

增广路: 在残存网络中,存在一条路径使得从S到T,该条路径流过的水>0,也就是路径最小值大于零,则称为该条路径成为增广路。

:对于原点S 和 汇点T,我们通过划分将点集分成两部分,满足 S点集中点可以从S到达,T点集中点可以到达T,但是T和S不能在一个集合,此时我们成为是流网络中的一个割。

割的容量: 即连接点集 S 和点集 T 的边权的容量

割的流量: 同上,边权的流量

最小割: 全称最小割的容量。

结论

  • 如果残留网络不存在增广路,此时流网络的可行流最大,即最大流

    证明: 主观十分好理解:如果不存在一种路径使得水从原点流到汇点,那么此时说明不能再流动,那么此时就是最大流量

    证明充要:也很显然,既然是最大流量,那么就不可能存在让自己流量在增加的路

  • 如果残留网络不存在增广路,那么一定存在一种割的两个点集 S,TS,TS,T,满足 ∣F∣=∑u∈S,v∈Tc(u,v)|F|=\sum_{u\in S,v\in T} c(u,v)F=uS,vTc(u,v)

    证明:现在画图更好理解这句话的意思:
    在这里插入图片描述

    公式含义就是粉色框的边权的容量要等于流网络的可行流。 y总的证明:

    我们这样构造 S点集:在残留网络GfG_fGf 中所有从S点出发边权容量不是 0 的点,剩余点归算于T点,那么对于这割的容量就是0,可以发现,这样的集合是可以构造出来的,因为有这个GfG_fGf 不存在增广路的条件,那么所有的路径至少存在一个边权容量为0。

    那么因为每个割的残留网络容量为0,那么说明每条边都是某条流过的路径的最小值(或者是某些最小值的和,因为可能会存在距离S更近的某条边残余容量为0,这些流量之后恰好分开流向后面的最小值们,本质上是还是所有流过的路径最小值的贡献总和,可以理解为是所有的最小值边成为了割),也就是该条路径的流量,那么将这些流量加起来就是总流量。因为从S点流出的流量必须经过割中的所有边权才能到达T,又因为每个边的容量等于流量,所以满足 ∣F∣=∑C(u,v)|F|=\sum C(u,v)F=C(u,v)

  • 如果存在两个点集 S,TS,TS,T,满足 ∣F∣=∑u∈S,v∈Tc(u,v)|F|=\sum_{u\in S,v\in T} c(u,v)F=uS,vTc(u,v),那么∣F∣|F|F 一定是最大流

证明:很显然,因为割的每条边的流量完全等于容量(由2可知(因为不存在增广路)),试想,我们将连接两个点集的之间割的容量全部得到了(S,T 之间可传递的最大容量),那么此时的流量和就是最大流。

  • 最大流=最小割

    由上面的三个证明便可以得到,最大流 →\to 不存在增广路 →\to 最小割 →\to 最大流

这是最重要的证明,便于以后的建边和理解。

最大流

算法实现:

EK算法

算法思想:在图中不断找增广路,然后对边更改删掉增光路,对答案造成贡献,最后不存在增广路就是答案。时间复杂度:O(nm2)O(nm^2)O(nm2),时间复杂度不会证明,不过可以满足 n<10000 以下的级别

模板:

#include<bits/stdc++.h>
#define int long long
using namespace std;
int read(){int x;scanf("%lld",&x);return x;}
const int B=1e6+10;
int p;
int n,m,S,T;
int head[1010];
int cnt;
struct node
{int u,v,nxt,w;
}e[B<<1];
void modify(int u,int v,int w)
{e[cnt].nxt=head[u];e[cnt].v=v;e[cnt].w=w;head[u]=cnt;cnt++;
} 
void add(int u,int v,int w)
{modify(u,v,w);modify(v,u,0);
}
int d[1010];
int pre[1010];
int vis[1010];
int inf=0x3f3f3f3f;
bool bfs()
{queue<int>q;memset(vis,0,sizeof(vis));q.push(S); vis[S]=1; d[S]=inf; while (!q.empty()){int u=q.front();q.pop();for (int i=head[u];i!=-1;i=e[i].nxt){int v=e[i].v;if (vis[v] || e[i].w==0) continue;d[v]=min(d[u],e[i].w);vis[v]=1;pre[v]=i;if (T==v) return 1;q.push(v);}}return 0;
}
int EK()
{int r=0;while (bfs()){r+=d[T];for (int i=T;i!=S;i=e[pre[i]^1].v){e[pre[i]].w-=d[T];e[pre[i]^1].w+=d[T];}}return r;
}
void work()
{cin>>n>>m>>S>>T;memset(head,-1,sizeof(head));for (int i=1;i<=m;i++){int u=read(),v=read(),c=read();add(u,v,c);}cout<<EK();
}
signed main()
{p=1;while (p--) work();return 0;
}

网络流细节

双向建边的时候需要注意的地方

  • cnt从 0 开始,不是1
  • for 循环里,边界 i!=-1 而不是 i!=0

费用流

费用流被称为:最小费用最大流

每个流量上又添加了一个单位流量花费。要求在最大流的情况下花费最小。

写法:把bfs找增广路,改写成SPFA找增广路就可以

证明:

最终可以从s点到达T做出贡献的路径是一定的,每条路径之间可能存在平替的现象,Bfs只是从中随便选择了几条,但我们可以将其平替,如下图

在这里插入图片描述

这些路径中,存在多种方案满足条件,要想花费最小,直接贪心就可。以前之所没有想明白,就可这张图一样,我想的是如果先把 6 的路径找出来,最后就只剩下 2 的路径才能成为8

不过我忘记了,还可以从 4 中走2,并不一定非要走4,所以这就不需要担心当前选择影响后续可选择对象的问题了,因为当前的选择不影响后续任何选择,不会导致选择对象数量发生变化,故可以直接贪心

模板

#include<bits/stdc++.h>
#define int long long
using namespace std;
int read(){int x;scanf("%lld",&x);return x;}
const int B=1e6+10;
int p;
int n,m,S,T;
int head[B];
int cnt;
struct node
{int u,v,nxt,c,w;
}e[B<<1];
void modify(int u,int v,int c,int w)
{e[cnt].nxt=head[u];e[cnt].v=v;e[cnt].c=c;e[cnt].w=w;head[u]=cnt;cnt++;
} 
void add(int u,int v,int c,int w)
{modify(u,v,c,w);modify(v,u,0,-w);
}
int d[5010];
int pre[5010];
int vis[5010];
int inf=0x3f3f3f3f;
int dis[5010];
bool bfs()
{queue<int>q;for (int i=1;i<=n;i++){vis[i]=0;dis[i]=inf;}q.push(S); vis[S]=1; d[S]=inf; dis[S]=0;while (!q.empty()){int u=q.front();q.pop();vis[u]=0;for (int i=head[u];i!=-1;i=e[i].nxt){int v=e[i].v;if (e[i].c==0) continue;if (dis[v]>dis[u]+e[i].w){dis[v]=dis[u]+e[i].w;pre[v]=i;d[v]=min(d[u],e[i].c);if (!vis[v]){vis[v]=1;q.push(v);}}}}return dis[T]!=inf;
}
void EK()
{int r=0;int sum=0;while (bfs()){r+=d[T];sum+=d[T]*dis[T];for (int i=T;i!=S;i=e[pre[i]^1].v){e[pre[i]].c-=d[T];e[pre[i]^1].c+=d[T];}}printf("%lld %lld", r,sum);
//	cout<<r<<" "<<sum<<"\n";
}
void work()
{cin>>n>>m>>S>>T;for (int i=1;i<=n;i++) head[i]=-1;for (int i=1;i<=m;i++){int u=read(),v=read(),c=read(),w=read();add(u,v,c,w);}EK();
}
signed main()
{p=1;while (p--) work();return 0;
}

r,sum);
// cout<<r<<" “<<sum<<”\n";
}
void work()
{
cin>>n>>m>>S>>T;
for (int i=1;i<=n;i++) head[i]=-1;
for (int i=1;i<=m;i++)
{
int u=read(),v=read(),c=read(),w=read();
add(u,v,c,w);
}
EK();
}
signed main()
{
p=1;
while (p–) work();
return 0;
}


http://www.dtcms.com/a/330449.html

相关文章:

  • 版本更新!FairGuard-Mac加固工具已上线!
  • 【Unity3D实例-功能-移动】角色行走和奔跑的相互切换
  • Unity2022 + URP + Highlight plus V21配置和使用
  • Linux下使用Samba 客户端访问 Samba 服务器的配置(Ubuntu Debian)
  • 一颗TTS语音芯片给产品增加智能语音播报能力
  • 【无标题】卷轴屏手机前瞻:三星/京东方柔性屏耐久性测试进展
  • python自学笔记8 二维和三维可视化
  • 【深度学习】深度学习基础概念与初识PyTorch
  • 【C#补全计划】泛型约束
  • 从0开始的中后台管理系统-7(订单列表功能实现,调用百度地图打点以及轨迹图动态展示)
  • 数据结构--------堆
  • 18.14 全量微调实战手册:7大核心配置提升工业级模型训练效率
  • 阿里云RDS SQL Server实例之间数据库迁移方案
  • 通信算法之313:FPGA中实现滑动相关消耗DSP资源及7045/7035的乘法器资源
  • 工具栏扩展应用接入说明
  • React和Vue
  • Webpack Plugin 深度解析:从原理到实战开发指南
  • 使用AI编程自动实现自动化操作
  • Java 设计模式-组合模式
  • python的艺术品收藏管理系统
  • 数学建模层次分析法(AHP)笔记
  • C++入门自学Day11-- List类型的自实现
  • 2025天府杯数学建模B题分析
  • Vite 为什么比 Webpack 快?原理深度分析
  • Mac 新电脑安装cocoapods报错ruby版本过低
  • 一周学会Matplotlib3 Python 数据可视化-绘制面积图(Area)
  • 如何用aiohttp实现每秒千次的网页抓取
  • 机器视觉的磁芯定位贴合应用
  • PHP现代化全栈开发:实时应用与WebSockets实践
  • JVM字节码文件结构