网络流学习笔记

news2024/12/24 14:28:55

网络流基础

基本概念

  • 源点(source) s s s,汇点 t t t

  • 容量:约等于边权。不存在的边流量可视为 0 0 0 ( u , v ) (u,v) (u,v) 的流量通常记为 c ( u , v ) c(u,v) c(u,v)(capacity)。

  • 流(flow):每条边上的流不能超过它的容量,注意这个限制是所有经过路径的边共享的。除了源点和汇点,其他所有点流入的流量都等于流出的流量。通常用 f f f 表示。

  • 割:把结点分成两部分 { S , T } \{S,T\} {S,T},且满足 s ∈ S , t ∈ T s\in S,t\in T sS,tT { S , T } \{S,T\} {S,T} 是图的一个 s s s- t t t 割, s s s- t t t { S , T } \{S,T\} {S,T} 的容量为 ∑ u ∈ S ∑ v ∈ T c ( u , v ) \sum\limits_{u\in S}\sum\limits_{v\in T}c(u,v) uSvTc(u,v)

  • 残留网络:有源点、汇点,且每条边都有残留容量的网络。

  • 增广路:从残留网络的源点到汇点的路径。对于增广路,给每一条边都加上等量流量,此过程称为增广。

常见问题

  • 最大流问题:给定每条边的流量,求得尽可能大的流量。
  • 最小割问题:给定每条边的流量,求一个容量尽可能大的 s s s- t t t { S , T } \{S,T\} {S,T}
  • 最小费用最大流问题:给定每条边的流量和权值(费用),求对于所有可能的最大流,费用最小的一个。
  • 上下界网络流问题:给定每条边的流量上界和下界,求一种可行的流使得满足限制。

最大流问题

以下代码均为 P3376 【模板】网络最大流 代码。

先介绍一种思想——Ford–Fulkerson 增广(FF 增广)。即不断在残留网络中找一条增广路,向汇点发送可能的最大流量,得到新的残留网络,不断寻找增广路,直到没有增广路为止。此时有最大流。

在 FF 增广的过程中,为了保证正确性,我们要引入反向边。对于每一条边 ( u , v ) (u,v) (u,v),建一条 c ( v , u ) = 0 c(v,u)=0 c(v,u)=0 的反向边。反向边其实相当于一种撤回操作,因此在增广的过程中,给正向边减去流量的同时要给反向边加上流量。

反向边的“抵消”操作使得在错误的增广路选择顺序下也可以得到正确答案。

FF 增广的时间复杂度为 O ( E f max ⁡ ) O(Ef_{\max}) O(Efmax)

EK 算法

通过 BFS 实现的 FF 增广过程。最坏时间复杂度为 O ( V E 2 ) O(VE^2) O(VE2),一般可以处理 1 0 4 10^4 104 规模的网络。

注意这里的链前 cnt 初始值要设定为 1 1 1,方便通过异或操作查找反向边(这样可以使第一条边的编号为偶数, 2 n ⊕ 1 = 2 n + 1 , ( 2 n + 1 ) ⊕ 1 = 2 n 2n \oplus 1=2n+1,(2n+1)\oplus 1=2n 2n1=2n+1,(2n+1)1=2n)。用 pre 数组记录当前的增广路,flow 数组记录当前增广路上的流量。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const ll maxn=5005;
int n,m,s,t,cnt=1,head[maxn],pre[maxn];
ll flow[maxn]/*记录到x的最大可行流*/,ans;
struct edge{int to,nxt;ll c;}e[maxn*2];
bool vis[maxn],flag[205][205];

void add(int x,int y,ll z){e[++cnt]={y,head[x],z},head[x]=cnt;}

bool bfs()
{
    for(int i=1;i<=n;i++) vis[i]=0;
    queue<int> q;
    vis[s]=1,q.push(s),flow[s]=LLONG_MAX;
    while(!q.empty())
    {
        int x=q.front();q.pop();
        for(int i=head[x];i;i=e[i].nxt)
        {
            if(!e[i].c) continue;
            if(vis[e[i].to]) continue;
            flow[e[i].to]=min(flow[x],e[i].c),pre[e[i].to]=i,q.push(e[i].to),vis[e[i].to]=1;
            if(e[i].to==t) return 1;
        }
    }
    return 0;
}

void ek()
{
    int x=t;
    while(x!=s) e[pre[x]].c-=flow[t],e[pre[x]^1].c+=flow[t],x=e[pre[x]^1].to;
    ans+=flow[t];
}

int main()
{
    cin>>n>>m>>s>>t;
	for(int i=1,u,v,w;i<=m;i++) 
	    cin>>u>>v>>w,add(u,v,w),add(v,u,0);
	while(bfs()) ek();
	cout<<ans;
	return 0;
}

Dinic 算法

先通过 BFS,把图根据结点到源点的距离分层,只按照层数递增的方向增广。注意每次增广后都要重新将图分层。

为了保证 Dinic 算法的时间复杂度正确性,我们需要引入当前弧优化。如果一条边 ( u , v ) (u,v) (u,v) 的容量已经用完,或 v v v 的后侧已经增广至阻塞,则 u u u 的流量无需流向出边 ( u , v ) (u,v) (u,v)。对于每个结点,维护它的出边中第一个需要尝试流出的出边。维护的这个指针称为当前弧。由于我们的边是顺次遍历的,所以当遍历到第 i i i 条边时,前面的边一定已经不能继续流,直接修改新的当前弧 now[x]=i

还可以用多路增广的方法优化时间复杂度。在某点找到一条增广路后,如果还有剩余流量,继续从该点寻找增广路。

DFS 过程中,对于当前结点 x x x,它可以分给后面结点最多 f max ⁡ f_{\max} fmax 流量;对于当前访问的边 ( u , v ) (u,v) (u,v),分配的流量是最大流量与已经用的流量之差与边的容量取 min ⁡ \min min 的结果。

最坏时间复杂度为 O ( V 2 E ) O(V^2E) O(V2E)

#include <bits/stdc++.h>
using namespace std;
#define int long long
typedef long long ll;

const int maxn=5005;
int n,m,s,t,cnt=1,head[maxn],now[maxn];
ll flow[maxn],ans,dis[205];
struct edge{int to,nxt;ll c;}e[maxn*2];

void add(int x,int y,ll z){e[++cnt]={y,head[x],z},head[x]=cnt;}

bool bfs()
{
    for(int i=1;i<=n;i++) dis[i]=LLONG_MAX;
    queue<int> q;
    q.push(s);dis[s]=0,now[s]=head[s];
    while(!q.empty())
    {
        int x=q.front();q.pop();
        for(int i=head[x];i;i=e[i].nxt)
            if(e[i].c>0&&dis[e[i].to]==LLONG_MAX)
            {
                q.push(e[i].to),now[e[i].to]=head[e[i].to],dis[e[i].to]=dis[x]+1;
                if(e[i].to==t) return 1;
            }
    }
    return 0;
}

int dfs(int x,ll mxf)//mxf是能给后面点分配的最大流量
{
    if(x==t) return mxf;
    ll sum=0;//sum是从x点实际分配出的流量
    for(int i=now[x];i;i=e[i].nxt)
    {
    	now[x]=i;
    	if(e[i].c>0&&dis[e[i].to]==dis[x]+1)
    	{
    		int ff=dfs(e[i].to,min(mxf-sum,e[i].c));
    		e[i].c-=ff,e[i^1].c+=ff,sum+=ff;
    		if(ff==mxf) return ff;
    	}
    }
    return sum;
}

signed main()
{
    cin>>n>>m>>s>>t;
	for(int i=1,u,v,w;i<=m;i++) 
	    cin>>u>>v>>w,add(u,v,w),add(v,u,0);
	while(bfs()) ans+=dfs(s,LLONG_MAX);
	cout<<ans;
	return 0;
}

最小费用最大流问题

( u , v ) (u,v) (u,v) 流量为 f ( u , v ) f(u,v) f(u,v) 时,花费的费用为 f ( u , v ) × w ( u , v ) f(u,v)\times w(u,v) f(u,v)×w(u,v),要求在最大化 ∑ ( u , v ) ∈ E f ( u , v ) \sum\limits_{(u,v)\in E}f(u,v) (u,v)Ef(u,v) 的情况下最小化 ∑ ( u , v ) ∈ E f ( u , v ) × w ( u , v ) \sum\limits_{(u,v)\in E} f(u,v)\times w(u,v) (u,v)Ef(u,v)×w(u,v),该问题即最小费用最大流问题。

SSP 算法

SSP(Successive Shortest Path)算法,思想是每次寻找费用最小的增广路进行增广,直到图上不存在增广路为止。

注意图中不能存在单位费用为负的圈。

具体实现就是把 EK/Dinic 算法中 BFS 找增广路的过程用 SPFA 代替,同时反向边的花费为负。时间复杂度 O ( V E f max ⁡ ) O(VEf_{\max}) O(VEfmax)

以下是基于 EK 算法的实现:

#include <bits/stdc++.h>
using namespace std;

const int maxn=5005,maxm=1e5+5;
struct edge{int to,nxt,c,w;}e[maxm];
int head[maxn],pre[maxn],dis[maxn],cnt=1,n,m,s,t,mxf,minc,flow[maxn];
bool vis[maxn];

void add(int x,int y,int z,int q){e[++cnt]=(edge){y,head[x],z,q},head[x]=cnt;}

bool spfa()
{
	queue<int> q;
	for(int i=1;i<=n;i++) dis[i]=INT_MAX,vis[i]=0;
	q.push(s),dis[s]=0,flow[s]=INT_MAX,vis[s]=1,pre[t]=-1;
	while(!q.empty())
	{
		int x=q.front();q.pop(),vis[x]=0;
		for(int i=head[x];i;i=e[i].nxt)
			if(e[i].c&&dis[e[i].to]>dis[x]+e[i].w)
			{
				dis[e[i].to]=dis[x]+e[i].w,pre[e[i].to]=i,flow[e[i].to]=min(flow[x],e[i].c);
				if(!vis[e[i].to]) q.push(e[i].to),vis[e[i].to]=1;
			}
	}
	return pre[t]!=-1;
}

void ek()
{
	while(spfa())
	{
		int x=t;
		mxf+=flow[t],minc+=flow[t]*dis[t];
		while(x!=s) e[pre[x]].c-=flow[t],e[pre[x]^1].c+=flow[t],x=e[pre[x]^1].to;
		// cout<<flow[t]<<' '<<dis[t]<<endl;
	}
}

int main()
{
	cin>>n>>m>>s>>t;
	for(int i=1,u,v,w,c;i<=m;i++) cin>>u>>v>>w>>c,add(u,v,w,c),add(v,u,0,-c);
	ek();
	cout<<mxf<<' '<<minc;
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1145132.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SDK 控件

目录 控件 控件创建 控件的消息处理 总代码 本篇文章对控件的学习&#xff0c;只是对基础的部分&#xff0c;简单的使用&#xff0c;包括消息的处理上&#xff0c;并不涉及深入的内容。 控件 区分控件&#xff0c;资源&#xff1a; SDK通常提供了一系列常用的用户界面控件…

san.js源码解读之模版解析(parseTemplate)篇——readIdent函数

一、源码分析 /*** 读取ident* 这里的 ident 指标识符(identifier)&#xff0c;也就是通常意义上的变量名* 这里默认的变量名规则为&#xff1a;由美元符号($)、数字、字母或者下划线(_)构成的字符串** inner* param {Walker} walker 源码读取对象* return {string}*/ functio…

虎去兔来(C++)

系列文章目录 进阶的卡莎C++_睡觉觉觉得的博客-CSDN博客数1的个数_睡觉觉觉得的博客-CSDN博客双精度浮点数的输入输出_睡觉觉觉得的博客-CSDN博客足球联赛积分_睡觉觉觉得的博客-CSDN博客大减价(一级)_睡觉觉觉得的博客-CSDN博客小写字母的判断_睡觉觉觉得的博客-CSDN博客纸币(…

python爬虫request和BeautifulSoup使用

request使用 1.安装request pip install request2.引入库 import requests3.编写代码 发送请求 我们通过以下代码可以打开豆瓣top250的网站 response requests.get(f"https://movie.douban.com/top250"&#xff09;但因为该网站加入了反爬机制&#xff0c;所以…

Python---练习:有一物,不知其数,三三数之余二,五五数之余三,七七数之余二,问物几何?

案例&#xff1a; 有一物&#xff0c;不知其数&#xff0c;三三数之余二&#xff0c;五五数之余三&#xff0c;七七数之余二&#xff0c;问物几何&#xff1f; 人话&#xff1a; 有一个数字&#xff0c;不知道具体是多少&#xff0c;用3去除剩2&#xff0c;用5去除剩3&#…

Vue 3.3.6 ,得益于WeakMap,比之前更快了

追忆往昔&#xff0c;穿越前朝&#xff0c;CSS也是当年前端三剑客之一&#xff0c;风光的很&#xff0c;随着前端跳跃式的变革&#xff0c;CSS在现代前端开发中似乎有点默默无闻起来。 不得不说当看到UnoCss之前&#xff0c;我甚至都还没听过原子化CSS[1]这个概念&#xff08;…

业界中说的快速原型法是什么

快速原型法是一种软件开发过程&#xff0c;其核心思想是在开发初期快速构建一个系统的原型&#xff0c;即一个工作模型&#xff0c;以便用户和开发者能够更好地理解系统的需求和功能。这种方法强调快速迭代和用户参与&#xff0c;目的是更早地发现和修正问题&#xff0c;从而提…

微软:Octo Tempest是最危险的金融黑客组织之一

导语 最近&#xff0c;微软发布了一份关于金融黑客组织Octo Tempest的详细报告。这个组织以其高级社交工程能力而闻名&#xff0c;专门针对从事数据勒索和勒索软件攻击的企业。Octo Tempest的攻击手段不断演变&#xff0c;目标范围也不断扩大&#xff0c;成为了电缆电信、电子邮…

回流重绘零负担,网页加载快如闪电

&#x1f3ac; 江城开朗的豌豆&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 &#x1f4dd; 个人网站 :《 江城开朗的豌豆&#x1fadb; 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! 目录 ⭐ 专栏简介 &#x1f4d8; 文章引言 一、回…

椭圆曲线点加的应用计算

一、点加应用 1.1 背景 假设一条椭圆曲线方程为 y^2 =x^3+ax+b确定这条椭圆曲线方程参数是p,a,b,G,n,h,除了参数a,b ,其他参数的意义 p为质数,(mod p)运算G为基点n为点G的阶h是椭圆曲线上所有点的个数m与n相除的商的整数部分1.2 方程(y^2 =x^3+x+6,P=11) 椭圆曲线方程y…

原始流,缓冲流性能比较

一.低级字节流一个一个字节复制 1.代码 package org.example;import java.io.*;public class day13 {//原视频路径private static final String file1 "D:\\temp\\day05\\改名.mp4";//目的视频路径private static final String file2 "D:\\temp\\day05\\不改…

消息队列中间件面试笔记总结RabbitMQ,Kafka,RocketMQ

文章目录 (一) Rabbit MQRabbitMQ 核心概念消息队列的作用Exchange(交换器)Broker&#xff08;消息中间件的服务节点&#xff09;如何保证消息的可靠性如何保证 RabbitMQ 消息的顺序性如何保证 RabbitMQ 高可用的&#xff1f;如何解决消息队列的延时以及过期失效问题消息堆积问…

服务运营 |论文解读: 住院病人“溢出”:一种近似动态规划方法

摘要 在住院床位管理中&#xff0c;医院通常会将住院病人分配到相对应的专科病房&#xff0c;但随着病人的入院和出院&#xff0c;可能会出现病人所需的专科病房满员&#xff0c;而其他病房却有空余床位的情况。于是就有了 "溢出 "策略&#xff0c;即当病人等候时间…

温湿度计传感器DHT11控制数码管显示verilog代码及视频

名称&#xff1a;温湿度计传感器DHT11控制数码管显示 软件&#xff1a;QuartusII 语言&#xff1a;Verilog 代码功能&#xff1a; 使用温湿度传感器DHT11采集环境的温度和湿度&#xff0c;并在数码管显示 本代码已在开发板验证 开发板资料&#xff1a; 大西瓜第一代FPGA升级…

leetCode 229. 多数元素 II + 摩尔投票法 + 进阶 + 优化空间

229. 多数元素 II - 力扣&#xff08;LeetCode&#xff09; 给定一个大小为 n 的整数数组&#xff0c;找出其中所有出现超过 ⌊ n/3 ⌋ 次的元素。 进阶&#xff1a;尝试设计时间复杂度为 O(n)、空间复杂度为 O(1)的算法解决此问题。 &#xff08;1&#xff09;哈希表 class …

Android-宝宝相册(第四次作业)

第四次作业-宝宝相册 题目 用Listview建立宝宝相册&#xff0c;相册内容及图片可自行设定&#xff0c;也可在资料文件中获取。给出模拟器仿真界面及代码截图。 &#xff08;参考例4-8&#xff09; 创建工程项目 创建名为baby的项目工程&#xff0c;最后的工程目录结构如下图所…

Linux 基本语句_8_C语言_文件控制

为了解决多个进程同时操作一个文件&#xff0c;产生一些情况&#xff0c;通常对文件进行上锁&#xff0c;已解决对共享文件的竞争 对打开文件进行各种操作&#xff1a; int fcentl(int fd, int cmd, .../*arg*/如果cmd与锁操作有关&#xff0c;那么fcentl函数的第三个参数就要…

从Mysql架构看一条查询sql的执行过程

1. 通信协议 我们的程序或者工具要操作数据库&#xff0c;第一步要做什么事情&#xff1f; 跟数据库建立连接。 首先&#xff0c;MySQL必须要运行一个服务&#xff0c;监听默认的3306端口。在我们开发系统跟第三方对接的时候&#xff0c;必须要弄清楚的有两件事。 第一个就是通…

38基于matlab的期货预测,利用PSO优化SVM和未优化的SVM进行对比,得到实际输出和期望输出结果。

基于matlab的期货预测&#xff0c;利用PSO优化SVM和未优化的SVM进行对比&#xff0c;得到实际输出和期望输出结果。线性核函数、多项式、RBF核函数三种核函数任意可选&#xff0c;并给出均方根误差&#xff0c;相对误差等结果&#xff0c;程序已调通&#xff0c;可直接运行。 3…

哈希算法:哈希算法在分布式系统中有哪些应用?

文章来源于极客时间前google工程师−王争专栏。 哈希算法除了上篇的四个应用&#xff08;安全加密、数据校验、唯一标识、散列函数&#xff09;&#xff0c;还有三种应用&#xff1a;负载均衡、数据分片、分布式存储。 这三个应用都跟分布式系统有关。哈希算法是如何解决这些分…