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

[学习笔记] 网络流

时间:2024-12-24 18:19:15浏览次数:3  
标签:费用 cnt 容量 int 网络 笔记 学习 vis dis

网络流,梳理一下然后看下 trick。

网络流主要难点在于建模,网络流很多 trick 现在已经很难有新意了。很多很好想的都是紫题,没啥含金量啊。

最大流

在残量网络中找到一条路径,设边集为 \(u\),要求满足 \(\min_{ x\in u} C_x ≠ 0\),即每条边残量皆不为 \(0\)。此时将这条路径流满,流量就会变大,一直到没有增广路时即为本图最大流。

· Dinic

注意需要反建边,才能保证结果正确。代码中的一些小 skill 值得注意。

  1. 使用 bfs 从源点进行一次分层,即求单源最短路。
  2. 使用 dfs 在图上多路增广。
  3. 如果没有增广路了,那么退出,否则回到 1

有一些优化。

  1. 当前弧优化:对于每一条已经流满了流量的边,记录下来,本次增广内不再访问。将一次增广时间复杂度严格控制到 \(O(nm)\) 以内。
  2. 点优化:对于一个已经流不出流量的点,记录下来,本次增广内不再访问。
  3. bfs 分层优化:一次分层中已经走到了汇点,则停止。

code :

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M=5010,N=210,inf=2e10;
int cnt,dis[N],first[N],head[N],ans;
int n,m,s,t;
bool vis[N];
struct edge
{
	int to,next,f;
}e[M*2];
void add(int u,int v,int w)
{
	e[++cnt].to=v;
	e[cnt].f=w;
	e[cnt].next=first[u];
	first[u]=cnt;
	
	e[++cnt].to=u;
	e[cnt].f=0;
	e[cnt].next=first[v];
	first[v]=cnt;
}
bool bfs(int u,int t)
{
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=n;i++)
		head[i]=first[i];
	queue<int> q;
	q.push(u);
	dis[u]=1;
	vis[u]=1;
	while(!q.empty())
	{
		int now=q.front();
		q.pop();
		for(int i=first[now];i;i=e[i].next)
		{
			int to=e[i].to;
			if(!vis[to]&&e[i].f>0)
			{
				q.push(to);
				vis[to]=1;
				dis[to]=dis[now]+1;
				if(to==t)return 1;
			}
		}
	}
	return 0;
}
int dfs(int now,int t,int flow)
{
	if(now==t)return flow;
	int temp=flow;
	for(int i=head[now];i;i=e[i].next)
	{
		int to=e[i].to;
		head[now]=i;
		if(e[i].f>0&&dis[to]==dis[now]+1)
		{
			int k=dfs(to,t,min(e[i].f,flow));
			if(k==0)dis[to]=inf;
			e[i].f-=k;
			e[i^1].f+=k;
			flow-=k;
			if(flow==0)return temp;
		}
	}
	return temp-flow;
}
signed main()
{
	cin>>n>>m>>s>>t;
	cnt=1;
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		add(u,v,w);
	}
	while(bfs(s,t))
	{
		ans+=dfs(s,t,inf);
	}
	cout<<ans;
	return 0;
}

最小割

割:割掉一些边使得源汇不连通。

最大流 = 最小割

证明:

  1. 最大流 \(\leq\) 最小割。所有的割必然要对流的每个路径上中的一条边割掉,所以所有的割必然大于等于最大流。
  2. 最大流 \(\ge\) 最小割。考虑我们求出了一个最大流,那么某些边残量网络上为 \(0\)。这些边一定分布成为一个割,否则仍然会有增广路。

最小割 = 最大流

费用流

将增广算法改成 dijkstra。将边权改为费用分层。

但是残量网络中可能存在负权边,所以需要用到一个势能 \(h\)。让 \(u\to v\) 的边权由 \(x\) 变为 \(x+h_u-h_v\)。

推导不想写,每次增广后使 \(h_{i}\) 都加上 \(dis_i\) 即可实现过程一直非负。

code :

#include<bits/stdc++.h>
using namespace std;
const int M=150010,inf=2e9,N=4010;
int n,h[N],dis[N],first[N],s,t,head[N],ans,cnt,m;
bool vis[N];
struct edge
{
	int next,to,val,cost;
}e[M];
struct node
{
	int x,dis;
	bool operator<(const node &x)const
	{
		return x.dis<dis;
	}
};
void add(int x,int y,int u,int v)
{
	e[++cnt].to=y;
	e[cnt].next=first[x];
	e[cnt].val=u;
	e[cnt].cost=v;
	first[x]=cnt;
	
	e[++cnt].to=x;
	e[cnt].next=first[y];
	e[cnt].val=0;
	e[cnt].cost=-v;
	first[y]=cnt;
}
bool dijkstra(int s,int t)
{
	for(int i=1;i<=n;i++)
		dis[i]=inf,head[i]=first[i];
	priority_queue<node> q;
	q.push({s,0});
	dis[s]=0;
	while(!q.empty())
	{
		int u=q.top().x,d=q.top().dis;
		q.pop();
		if(d>dis[u])continue;
		for(int i=first[u];i;i=e[i].next)
		{
			int to=e[i].to;
			if(e[i].val&&d+e[i].cost+h[u]-h[to]<dis[to])
			{
				dis[to]=d+e[i].cost+h[u]-h[to];
				q.push({to,dis[to]});
			}
		}
	}
	return (dis[t]<inf);
}
int dfs(int u,int flow)
{
	int temp=flow;
	if(u==t||!flow)return flow;
	vis[u]=1;
	for(int i=head[u];i;i=e[i].next)
	{
		head[u]=i;
		int v=e[i].to,res=0;
		if(!vis[v]&&dis[v]==dis[u]+e[i].cost+h[u]-h[v]&&e[i].val&&(res=dfs(v,min(flow,e[i].val)))>0)
		{
			ans+=res*e[i].cost;
			e[i].val-=res;
			e[i^1].val+=res;
			flow-=res;
			if(flow==0)return temp;
		}
	}
	vis[u]=0;
	return temp-flow;
}
int main()
{
	cnt=1;
	cin>>n>>m;
	t=n;
	for(int i=1;i<=m;i++)
	{
		int a,b,c,d;
		cin>>a>>b>>c>>d;
		add(a,b,c,d);
	}
	int f=0;
	while(dijkstra(1,n))
	{
		memset(vis,0,sizeof(vis));
		f+=dfs(1,inf);
		for(int i=1;i<=n;i++)
			h[i]+=dis[i];
	}
	cout<<f<<" "<<ans;
	return 0;
}

无源汇上下界可行流

首先每条边流满下界 \(l\),然后建立一个新的图,各边流量为上界 \(r\) 与下界 \(l\) 之差 \(r-l\)。但是可能出现流量不平衡的情况。设立超级源点 \(S\),超级汇点 \(T\)。对于一个点:

  1. 该点入流比出流大,向 \(T\) 连一条容量为入流减出流的边。
  2. 该点出流比入流大,从 \(S\) 向该点连一条容量为出流减入流的边。
  3. 该点入流等于出流,不做操作。

然后跑最大流,如果源点流量流满则有可行流。

有源汇上下界可行流

在之前的基础上,将题目给出的源汇点连 \((t,s,inf)\) 的边,使之满足流量平衡。

有源汇上下界最大流

可行流中新连的 \((t,s,inf)\) 此时的流量即为一个可行流。然而可行流未必最大,需要在第一次的 \(Dinic(S,T)\) 后再来一次 \(Dinic(s,t)\)。

code :

#include<bits/stdc++.h>
using namespace std;
const int N=3010;
int cnt=1,n,m,s,t,tot,S,T,ans,inf=2147483647;
int num[N],fir[N],dis[N],vis[N],cur[N];
struct edge
{
	int to,w,nextt;
}e[200010];
void add(int u,int v,int w)
{
	e[++cnt].to=v,e[cnt].w=w,e[cnt].nextt=fir[u],fir[u]=cnt;
	e[++cnt].to=u,e[cnt].w=0,e[cnt].nextt=fir[v],fir[v]=cnt;
}
bool bfs()
{
	for(int i=0;i<=n+1;i++)
		dis[i]=0,vis[i]=false,cur[i]=fir[i];
	vis[S]=true,dis[S]=1;
	queue<int>q;
	q.push(S);
	while(!q.empty())
    {
		int u=q.front();
		q.pop();
		for(int i=fir[u];i;i=e[i].nextt)
        {
			int v=e[i].to;
			if(vis[v]||e[i].w<=0)continue;
			dis[v]=dis[u]+1,vis[v]=1;
			if(v==T)return 1;
			q.push(v);
		}
	}
	return 0;
}
int dfs(int u,int flow)
{
	if(!flow||u==T)return flow;
	int used=0;
	for(int i=cur[u];i;i=e[i].nextt)
    {
		int v=e[i].to;
		cur[u]=i;
		if(dis[u]+1!=dis[v])continue;
		int t=dfs(v,min(flow-used,e[i].w));
		if(!t)continue;
		e[i].w-=t,e[i^1].w+=t;
		used+=t;
		if(used==flow)return flow;
	}
	return used;
}
int dinic()
{
	int sum=0;
	while(bfs())
		sum+=dfs(S,inf);
	return sum;
}
int main()
{
    cin>>n>>m>>s>>t;
	S=0,T=n+1;
	for(int i=1;i<=m;i++)
    {
		int u,v,low,upp;
        cin>>u>>v>>low>>upp;
		add(u,v,upp-low);
		num[v]+=low,num[u]-=low;
	}
	for(int i=1;i<=n;i++)
    {
		if(num[i]>0)
        {
			add(S,i,num[i]);
			tot+=num[i];
		}
		if(num[i]<0) 
			add(i,T,-num[i]);
	}
	add(t,s,inf);
	ans=dinic();
	if(ans<tot)
    {
		printf("please go home to sleep");
		return 0;
	}
	ans=e[cnt].w;
	S=s,T=t;
	e[cnt].w=e[cnt-1].w=0;
	ans+=dinic();
    cout<<ans;
	return 0;
}

答案即为 \(Dinic(s,t)\) 加上可行流。

可行流中新连的 \((t,s,inf)\) 此时的流量即为一个可行流。然而可行流未必最小,需要在第一次的 \(Dinic(S,T)\) 后再来一次 \(Dinic(t,s)\)。

答案即为可行流减去 \(Dinic(s,t)\) 。

有源汇上下界费用流

最大流换成费用流。

网络流 tricks & 典题

最大流の基本建模

对于此类建模问题,要考虑清楚一个流代表什么,其最大流代表什么,每一条边的容量代表什么,细细思考之后再开始码。

  1. P2754 [CTSC1999] 家园 / 星际转移问题

唯一难点只有周期性停靠太空站,不然就是傻逼题了。按时间分层,跑最大流。

这个图就很好。这题中的流代表人,相同一个地点要向不同时间下的自己连边,注意只能从早的连到晚的,不然就时空穿梭了草。

  1. P2472 [SCOI2007] 蜥蜴

从一个柱子连到所有能跳到的柱子,容量为石柱耐久。因为每一只蜥蜴都不会跳回头路,所以每一点耐久都可以对应一个蜥蜴。源点连向所有有蜥蜴的柱子,所有能直接逃出界外的柱子连向汇点。

答案为最大流。

  1. P3254 圆桌问题

\(n+m\) 个点,代表 \(m\) 个单位与 \(n\) 个桌子。因为每个单位只有一个人能去到一个桌子,所以每个单位向每个桌子连容量为 \(1\) 的边。源点向每个桌子连容量为单位人数的边。如果最大流为人数之和则有可行方案,否则没有。本题中流代表人。

  1. P2764 最小路径覆盖问题

这是一类模型。

将图中每个点拆开,将一条路径视为合并两个路径。如果有一条边 \((x,y)\) 在原图中,那么连接 \((x,y+n)\),然后跑最大流。(其实就相当于一个二分图最大匹配)

模拟一下可以发现,这样跑最大流是满足条件的。而且只有没有合并的路径答案没有被计算上,所以答案为 \(n\) 减去最大流。

本题中的流意为将两个已经存在的路劲合并起来。

费用流の基本建模

  1. P2770 航空路线问题

拆点使只被访问一次,拆出来的两个点容量为 \(1\),费用为 \(1\)。将图中的边连上,注意只能从西边的城市走到东边的城市。第一个城市和最后一个城市拆出的边容量为 \(2\),费用为 \(1\) 的边。然后超级源点向最西边城市连容量为 \(2\),费用为 \(0\) 的边,最东边城市向汇点连容量为 \(2\),费用为 \(0\) 的边。跑最大费用最大流。最多城市即为费用。

此处的流代表选择这条边去走,费用代表走过这个城市。求和的东西一般要用费用来代表。

9.13

最大流の基本建模

  1. P2763 试题库问题

每个试题向每种属性连容量为 \(1\) 的边,每种属性向汇点连容量为该属性所需的边。方案输出就检查流量为 \(0\) 的边。

  1. P2766 最长不下降子序列问题

朴素 dp 解决第一问。如果 \(f_i \to f_j\) 是最优转移,则连边,这样每个流代表一个最长不降子序列,然后跑最大流。那些麻烦的限制就拆点然后限制流量。

  1. P4251 [SCOI2015] 小凸玩矩阵

二分 \(lim\),然后把该问题变成是否能找出至少 \(n-k+1\) 个数,使得这些数不大于当前 \(lim\) 且行列不同。最大流即可,源点向行连边,列向汇点连边,当前满足条件的数行列连边,然后判断最大流是否大于等于 \(n-k+1\)。

费用流の基本建模

  1. P1251 餐巾计划问题

首先,我们拆点,将一天拆成晚上和早上,每天晚上会受到脏餐巾(来源:当天早上用完的餐巾,在这道题中可理解为从原点获得),每天早上又有干净的餐巾(来源:购买、快洗店、慢洗店)。

从源点向每天晚上连一条费用为 \(0\),流量为当天所用餐巾的边,因为白天用完一定会剩下这么多脏餐巾。快洗就从 \(x\) 天晚上连到第 \(x+m\) 天早上,流量无限,费用为 \(f\),慢洗同理。放着不洗就连到下一天的晚上。从每天早上连向汇点,表示使用。流量流满则用完。从源点向每天早上连流量无限,费用为 \(p\) 的边。跑最小费用最大流。费用即为答案。

  1. P3356 火星探险问题

从一个格子连向能走到的格子,容量为无限。把有石头的格子拆成入点和出点,入点到出点容量为 \(1\),费用为 \(1\),对于能走到石头格子的格子,连向石头格子的入点,容量为 \(1\),连向石头格子的出点,容量为无限。最大费用最大流,流量为最多的能走的探测车,费用为石头数量。

  1. P3159 [CQOI2012] 交换棋子

将源点连向所有当前为 \(1\) 的点,将所有目标为 \(1\) 的点连向汇点,拆点,入点出点连边,容量皆为 \(1\)。对于每一个点,都拆开,费用为 \(1\),容量为 \(\frac{w_{i,j}}{2}\),如果这个点刚开始或者目标为黑色,那么容量为 \(1\),因为只可能被交换一次。每一个格子向八联通的点连容量无限,费用为 \(1\) 的边。流量意味着把黑点与自己交换过去,费用就是交换次数。跑最小费用最大流。最后答案即为费用

最小割の基本建模

此类问题,可以将思路视为“先全部选择,然后去掉一些东西使得满足某种条件”,即割。割的值最小即答案最大。

  1. P2762 太空飞行计划问题

这样子连边。答案即为所有实验得到收益之和减去最小割。考虑割了一些器材的边相当于选择了那个实验,割了实验的边相当于不选择实验。

  1. P2774 方格取数问题

可以先取完所有点,然后考虑最小割割到满足条件为止。横纵坐标奇偶性相同的点放在二分图的一侧,对于相斥的那些点连边,容量无限。\(s\) 向这些点连边,边权为该点权。其他的点向 \(t\) 连边,边权为该点权。割掉一条边则相当于选择与他相斥的边(当然以后有可能因为别的东西被割掉)

答案即为所有点权减去最小割即可,P4474 王者之剑 同理。

UPD:很多黑白染色问题都是这样的,懒得写了。大概知道一下。

标签:费用,cnt,容量,int,网络,笔记,学习,vis,dis
From: https://www.cnblogs.com/WindChime/p/18628459

相关文章

  • OrayUSBVHCI 驱动程序通常与 USB 虚拟主机控制器接口 (VHCI) 技术相关,这意味着它可
    OrayUSBVHCI是由上海贝斯特网络信息技术有限公司(ShanghaiBestOrayInformationTechnologyCo.,Ltd.)开发的一个USB驱动程序。它的版本是1.0.0.0,发布时间为2023年3月8日。OrayUSBVHCI驱动程序简介功能:OrayUSBVHCI 驱动程序通常与 USB虚拟主机控制器接口(VHCI)......
  • 漏洞扫描:网络安全的 “体检” 与 “防护指南”
    在当今数字化时代,网络安全如同守护城堡的坚固城墙,而漏洞扫描则是检查城墙是否存在缝隙与薄弱环节的重要手段。那么,究竟什么是漏洞扫描?又该如何进行呢?什么是漏洞扫描?漏洞扫描是一种安全检测过程,旨在识别计算机系统、网络或应用程序中的安全漏洞。这些漏洞可能被恶意用户利用以获......
  • 【学习笔记】平衡树
    介绍平衡树是一种特殊的二叉树搜树,他能在被修改后,依靠分裂,合并,等操作使得树能始终保持平衡(每一个节点的两棵子树的大小尽量相等),这里主要讲解FHQtreap。操作FHQtreap也叫无旋treap,他的每个节点有两个值\(val,pri\),其中\(pri\)满足二叉堆的性质,而\(val\)满足BST的性质......
  • 网络安全渗透实战!记一次攻防演练渗透测试实战,黑客技术零基础入门到实战教程!
    1、外网打点资产发现多测绘平台搜索https://hunter.qianxin.com/https://fofa.info/https://quake.360.cn/多语法搜索我给大家准备了一份全套的《网络安全入门+进阶学习资源包》包含各种常用工具和黑客技术电子书以及视频教程,需要的小伙伴可以扫描下方二维码或链接......
  • 【网络安全渗透测试零基础入门】之什么是文件包含漏洞&分类(非常详细)收藏这一篇就够了!
    一、前言大家好,我是强哥今天主要给大家讲解一下什么是文件包含漏洞、本地文件包含漏洞喜欢的朋友们,记得给我点赞支持和收藏一下,关注我,学习黑客技术。一、什么是文件包含漏洞1.文件包含漏洞概述和SQL注入等攻击方式一样,文件包含漏洞也是一种注入型漏洞,其本质就是输入......
  • 从实战的角度分析渗透测试究竟需要学习了解的知识点,黑客技术零基础入门到精通教程建议
    前言最近有很多人询问,自己明明OWASPTop10都学的差不多了,各种靶场也复现的差不多了,Burpsuite、goby、awvs、dirsearch等等工具也是用的丝滑,但为什么就是感觉挖不到洞呢基础知识已经准备的差不多了,现在可能缺乏的是挖洞时间的思路,针对特定场景下的渗透套路,这个一般可以学......
  • 深度探秘神经网络模型:核心要点、多样类型与实践应用
    基本概念神经元与生物启发:人工神经网络受人类大脑中的生物神经元启发,生物神经元由细胞体、树突和轴突等组成,可处于兴奋或抑制状态,通过突触传递信息。神经网络组成:由大量相互连接的神经元组成,包括输入层接收数据、隐藏层处理数据、输出层产生最终结果,各层神经元通过权重连接,还有......
  • 重庆市某区教委城域网网络管理与态势感知项目
        重庆市某区教育委员会是区政府直辖的一级政府职能部门,主要负责本区的教育工作。项目现状    重庆市某区教育委员会肩负着该地区众多学校和教育机构的信息化重任。随着全区教育数字化转型进程的不断推进,如何确保城域网的稳定性与高效性运作已成为其核心关注......
  • 机器学习:线性回归:最小二乘法应用一元线性回归(持续更新)
    目录前言(基础知识的准备最小二乘法在回归中的应用)利用最小二乘法解决最简单的一元线性回归问题第一步:引入必要的库并且创建数据集(这里使用的例子是房价与面积的关系)第二步利用某些方法去用一条直线去拟合你的数据第三步观察与测评求出的W,B值与数据集的拟合程度并且......
  • 机器学习:线性回归:梯度下降法应用多元线性回归(持续更新)
    目录第二节梯度下降法在线性回归中的应用情景带入这里提出误差函数即残差函数的概念:我们这里采用MSE损失函数来刻画预测值与真实值之间的误差大小下面是基于梯度下降法求解线性回归方程中参数(θ)(θ)的推导过程:于是我们重复的过程是:我们先观察各个特征数据与房价的......