Living-Dream 系列笔记 第93期

news/2025/1/18 16:53:18/文章来源:https://www.cnblogs.com/XOF-0-0/p/18678545

本文讲解 EK & Dinic 算法。

最大流

最大流的模型:

image

特别注意:这个流量上限不是单次流量不超过它,而是多次的总和不超过它。

EK

显然这个问题是可以使用 dfs 解决的,但是效率低下。

考虑如下的图。

image

我们发现 dfs 有可能走了 \(S \to A \to B \to T\) 这样一条路线,这会导致 \(B \to T\) 流量上限减小 \(1\),从而跑不出最优解,于是我们又需要新一轮的 dfs 来寻找最优解。这便是 dfs 效率低下的原因,即每次犯错都需要花费一轮或几轮的重新 dfs 来寻找最优解。

这启发我们思考,如何在一轮 dfs 中纠错?考虑建立反边,流量上限初始为 \(0\)。正边每次损耗多少流量,反边就加上多少流量。以上图为例,这样,如果 dfs 走了 \(S \to A \to B \to T\),它就可以通过走 \(S \to B \to A \to T\) 来纠正它的错误,相当于 \(S \to A \to T\)\(S \to B \to T\) 都跑了一遍 \(1\) 的流量。重复 \(100\) 次,即可在一轮 dfs 中求出最优解。这被称之为 FF 算法。

但是,很容易发现 FF 算法的时间复杂度取决于中间那条边的流量大小,当流量上限较大时,此算法的时间复杂度仍然很高。

有了纠错机制还不够,那么我们如何让 dfs 少犯错?考虑一种简单的贪心,我们可以总是走更短的路线,这样受的约束更少,更容易找到最优解。事实上,这个优化虽然看上去很 navie,但是它跑得飞快。找最短路用 bfs 即可解决。

综上便是 EK 算法的全部内容。

Dinic

考虑上述问题的一个特殊情况,如图。

image

如果使用 EK 算法求解最大流,它会不停地走 \(S \to 1 \to 2 \to ... \to 1000 \to x \to T(10^3 < x \le 10^4)\) 这条路径,前面的链被重复走了很多次,效率低下。

我们能否走到 \(T\) 之后不是回到 \(S\),而是回到 \(1000\)?想要实现回溯,就得使用 dfs。这里,我们采用 dfs(FF 算法)与 bfs(EK 算法)的结合体——Dinic 算法解决此类情形。

具体地,我们在每一轮寻找之前先进行一遍 bfs,确定每个点的「层数」(即源点到它的最短距离)。

在 dfs 中,我们只需要每次去到下一个节点的时候,保证层数严格递增即可实现同 EK 算法一样的效果。

同时,当我们走过一条边以后,要么它的流量被榨干了,要么我们自己的流量被榨干了,于是它完全没有了利用价值,下次应该从它的下一条边开始,从而优化效率。这被称为当前弧优化。

具体实现细节详见代码。

P3376

模板。

EK code
#include<bits/stdc++.h>
#define int long long
using namespace std;const int N=1e4+5;
int n,m,s,t;
int maxflow;
int inc[N],edge[N],to[N],pre[N];
bool vis[N];
struct EDGE{int v,w,i;
};
vector<EDGE> G[N];bool bfs(){memset(vis,0,sizeof vis);queue<int> q;vis[s]=1;inc[s]=0x3f3f3f3f;	q.push(s);while(!q.empty()){int cur=q.front();q.pop();for(auto nxt:G[cur]){if(edge[nxt.i]){if(vis[nxt.v])continue;inc[nxt.v]=min(inc[cur],edge[nxt.i]);pre[nxt.v]=nxt.i;vis[nxt.v]=1;q.push(nxt.v);if(nxt.v==t)return 1;}}}return 0;
}
void update(){maxflow+=inc[t];int cur=t;while(cur!=s){int last=pre[cur];edge[last]-=inc[t];edge[last^1]+=inc[t];cur=to[last^1];}
}signed main(){ios::sync_with_stdio(0);cin.tie(0);cin>>n>>m>>s>>t;for(int i=0,u,v,w;i<2*m;i+=2){cin>>u>>v>>w;G[u].push_back({v,w,i});edge[i]=w,to[i]=v;G[v].push_back({u,w,i^1});to[i^1]=u;}while(bfs())update();cout<<maxflow;return 0;
}
Dinic code
#include<bits/stdc++.h>
#define int long long
using namespace std;const int N=1e4+5;
int n,m,s,t;
int maxflow,eid;
int edge[N],level[N],sta[N];
struct EDGE{int v,w,i;
};
vector<EDGE> G[N];void add(int u,int v,int w,int i){G[u].push_back({v,w,eid});edge[eid]=w,eid++;G[v].push_back({u,0,eid});edge[eid]=0,eid++;
}
bool bfs(){memset(level,0,sizeof level);queue<int> q;q.push(s);level[s]=1;while(!q.empty()){int cur=q.front();q.pop();sta[cur]=0;for(auto nxt:G[cur]){if(edge[nxt.i]&&!level[nxt.v]){level[nxt.v]=level[cur]+1;q.push(nxt.v);if(nxt.v==t)return 1;}}}return 0;
}
int dinic(int cur,int flow){if(cur==t)return flow;int rest=flow;for(int x=sta[cur];x<G[cur].size();x++){auto nxt=G[cur][x];sta[cur]=x;if(rest&&edge[nxt.i]&&level[nxt.v]==level[cur]+1){int inc=dinic(nxt.v,min(rest,edge[nxt.i]));if(!inc)level[nxt.v]=0;edge[nxt.i]-=inc;edge[nxt.i^1]+=inc;rest-=inc;}}return flow-rest;
}signed main(){ios::sync_with_stdio(0);cin.tie(0);cin>>n>>m>>s>>t;for(int i=1,u,v,w;i<=m;i++){cin>>u>>v>>w;add(u,v,w,i);}while(bfs())maxflow+=dinic(s,0x3f3f3f3f);cout<<maxflow;return 0;
}

P2065

显然最大匹配是可以做的,不过会 T 飞。

看到这种匹配问题,考虑网络流建模。虚拟一个源点、汇点,将源点连蓝卡、蓝卡连红卡(要求 \(\gcd>1\))、红卡连汇点,边权均为 \(1\)(不能重复使用),然后跑最大流即可。但还是 T 飞了。

容易发现,根本原因是边太多了(接近 25w 条),这显然是饥饿和 EK 都无法接受的。

考虑到两个数 \(\gcd >1\) 必定有公质因子,于是将每个数进行质因数分解,然后连向各自的质因子。因为数 \(<10^7\),于是不同的质因子不会超过 \(10\) 个,这样点没增加多少,边却减少了很多。

code
//法二:网络流
#include<bits/stdc++.h>
#define int long long
using namespace std;const int N=1e4+5,M=1e5+5;
int T,n,m,s,t,ptot,maxflow,eid;
int inc[N],edge[M],to[M],pre[N],pid[M];
bool vis[N];
struct EDGE{int v,w,i;
};
vector<EDGE> G[N];void adde(int u,int v,int w){G[u].push_back({v,w,eid});edge[eid]=w,to[eid]=v,eid++;G[v].push_back({u,0,eid});edge[eid]=0,to[eid]=u,eid++;
}
void fuckit(int cur,int num,int typ){for(int i=2;i*i<=num;i++){if(num%i==0){while(num%i==0)num/=i;if(!pid[i])pid[i]=++ptot;if(!typ)adde(cur,n+m+pid[i],1);elseadde(n+m+pid[i],cur,1);}}if(num>1){if(!pid[num])pid[num]=++ptot;if(!typ)adde(cur,n+m+pid[num],1);elseadde(n+m+pid[num],cur,1);}
}
bool bfs(){memset(vis,0,sizeof vis);queue<int> q;vis[s]=1;inc[s]=0x3f3f3f3f;	q.push(s);while(!q.empty()){int cur=q.front();q.pop();for(auto nxt:G[cur]){if(edge[nxt.i]&&!vis[nxt.v]){inc[nxt.v]=min(inc[cur],edge[nxt.i]);pre[nxt.v]=nxt.i;vis[nxt.v]=1;q.push(nxt.v);if(nxt.v==t)return 1;}}}return 0;
}
void update(){maxflow+=inc[t];int cur=t;while(cur!=s){int last=pre[cur];edge[last]-=inc[t];edge[last^1]+=inc[t];cur=to[last^1];}
}signed main(){ios::sync_with_stdio(0);cin.tie(0);cin>>T;while(T--){eid=0;for(int i=0;i<N;i++)G[i].clear();cin>>m>>n;for(int i=1;i<=m;i++)adde(s,i,1);memset(pid,0,sizeof pid);ptot=0;for(int i=1,x;i<=m;i++)cin>>x,fuckit(i,x,0);for(int i=1,x;i<=n;i++)cin>>x,fuckit(i+m,x,1);t=n+m+ptot+1;for(int i=1;i<=n;i++)adde(i+m,t,1);maxflow=0;memset(inc,0,sizeof inc);while(bfs())update();cout<<maxflow<<'\n';}return 0;
}

P2472

有限制条件且有路径,考虑网络流。又要求逃出去的最多,考虑最大流。

石柱的高度是限制条件,肯定得作边权,于是我们把一个格子拆成两个点(入点、出点)即可。

虚拟一个源点、汇点,源点连起点,边权 \(1\)(最多所有蜥蜴都逃出去);每石柱入点连出点,边权为石柱高度;每个石柱的出点连和它距离不超过 \(d\) 的格子,边权为 \(\infty\);每个石柱若它能跳出界外,则与汇点连边,边权为 \(\infty\)

code
#include<bits/stdc++.h>
#define int long long
using namespace std;const int N=1e3+5,M=1e5+5;
const int INF=0x3f3f3f3f;
int r,c,s,t,d;
int tots,maxflow,eid;
int edge[M],sta[N],level[N];
int h[N][N];
char mp[N][N];
struct EDGE{int v,w,i;
};
vector<EDGE> G[N];
int st[N];int get(int x,int y){return (x-1)*c+y;
}
int get_dis(int x,int y,int xx,int yy){return (x-xx)*(x-xx)+(y-yy)*(y-yy);
}
void add(int x,int y,int w){G[x].push_back({y,w,eid});edge[eid]=w,eid++;
}
bool bfs(){memset(level,0,sizeof level);queue<int> q;level[s]=1;q.push(s);while(!q.empty()){int cur=q.front();sta[cur]=0;q.pop();for(auto nxt:G[cur]){if(edge[nxt.i]&&!level[nxt.v]){level[nxt.v]=level[cur]+1;q.push(nxt.v);if(nxt.v==t)return 1;}}}return 0;
}
int dinic(int cur,int flow){if(cur==t)return flow;int rest=flow;for(int i=sta[cur];i<G[cur].size();i++){auto nxt=G[cur][i];sta[cur]=i;if(rest&&edge[nxt.i]&&level[nxt.v]==level[cur]+1){int inc=dinic(nxt.v,min(edge[nxt.i],rest));if(!inc)level[nxt.v]=0;edge[nxt.i]-=inc;edge[nxt.i^1]+=inc;rest-=inc;}}return flow-rest;
}signed main(){ios::sync_with_stdio(0);cin.tie(0);cin>>r>>c>>d;for(int i=1;i<=r;i++){for(int j=1;j<=c;j++){char ch;cin>>ch,h[i][j]=ch-'0';}}for(int i=1;i<=r;i++){for(int j=1;j<=c;j++){cin>>mp[i][j];if(mp[i][j]=='L')st[++tots]=get(i,j);}}for(int i=1;i<=tots;i++)add(s,st[i],1),add(st[i],s,0);for(int i=1;i<=r;i++)for(int j=1;j<=c;j++)for(int ii=1;ii<=r;ii++)for(int jj=1;jj<=c;jj++)if((i!=ii||j!=jj)&&h[i][j]&&h[ii][jj]&&get_dis(i,j,ii,jj)<=d*d)add(get(i,j)+r*c,get(ii,jj),INF),add(get(ii,jj),get(i,j)+r*c,0);for(int i=1;i<=r;i++)for(int j=1;j<=c;j++)if(h[i][j])add(get(i,j),get(i,j)+r*c,h[i][j]),add(get(i,j)+r*c,get(i,j),0);t=2*r*c+1;for(int i=1;i<=r;i++)for(int j=1;j<=c;j++)if(h[i][j]&&(j<=d||c-j+1<=d||i<=d||r-i+1<=d))add(get(i,j)+r*c,t,INF),add(t,get(i,j)+r*c,0);while(bfs())maxflow+=dinic(s,INF);cout<<tots-maxflow;return 0;
}

总结:

  • 做题可以先想暴力解法,然后思考其慢在哪里,从而找到突破点。

  • 限制条件、匹配问题考虑网络流(最大流)。

  • 建模善用拆点技巧,且建完之后一定要画出来验证正确性。

  • 写代码之前先在草稿纸上设计代码,至少也得在脑子里过一遍框架。

习题:

  • P2763

  • P1231

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【每日一题】20250118

我是时间唯一的主人。成为自己的时间的主人是一种奢侈。我认为这是人类能够送给自己的最奢侈的东西之一。【每日一题】 1.(16分) \(\hspace{0.6cm}\)如图所示,在以坐标原点 \(O\) 为圆心、半径为 \(R\) 的半圆形区域内,有相互垂直的匀强电场和匀强磁场,磁感应强度为 \(B\),…

思通数科舆情监测系统:精准实现数据监测与实时预警的应用意义

随着信息化社会的深入发展,舆情管理变得愈加复杂,尤其是在社交媒体和网络平台的广泛应用下,信息传播的速度与影响力呈现出指数级增长。如何高效监测和分析这些海量数据,成为各级政府、企业和公共机构亟待解决的问题。思通数科的舆情监测系统,凭借强大的数据监控与分析能力…

中考英语优秀范文-热点话题-传统文化-009 Dragon Boat Festival 端午节

1 写作要求 为弘扬中华传统文化,增强文化自觉,学校将举行一次英语演讲比赛。请以“ ___________Festival”为题,写一篇演讲稿,介绍一个你最喜欢的中国传统节日。 提示问题: What is your favorite traditional festival? Can you say some basic facts about it? How do…

在线json调试工具

在线json格式化工具,无需登录,打开即用 https://json.openai2025.com/

在线base64工具

在线base64工具,不需登录,打开即用 base64编码和解码功能。 https://base64.openai2025.com/

图像的卷积处理

实验名称:图像的卷积处理 实验描述:包含图像的平滑卷积和边缘卷积,通过实验观察和理解三种平滑卷积的差异性、理解边缘卷积提取图像边缘特征的作用。 实验步骤 一、平滑卷积 1. 加载图像并可视化 2. 生成带有雪花噪声的图像 3. 用均值卷积去噪声 4. 用中值卷积去噪 5. 用高斯…

从单数据源到多数据源的探讨

今天我想简单地分享一下如何将一个老项目从单数据源切换为多数据源的过程。这个项目是一个使用 WAR 部署的传统 JSP Web 项目,运行在 JDK 1.7 环境下,项目中并没有使用 Spring Boot,而仅仅采用了 Spring MVC 框架。我的主要任务是将原本使用单一数据源的架构,升级为支持多数…

eclipse thymeleaf 离线安装

下载zip包 https://github.com/thymeleaf/thymeleaf-extras-eclipse-plugin/releases 选择zip包参考 https://www.cnblogs.com/jiduoduo/p/15525430.html

Cisco ISR 1000 Series IOS XE Release 17.16.1a ED

Cisco ISR 1000 Series IOS XE Release 17.16.1a EDCisco ISR 1000 Series IOS XE Release 17.16.1a ED 思科 1000 系列集成多业务路由器 IOS XE 系统软件 请访问原文链接:https://sysin.org/blog/cisco-isr-1000/ 查看最新版。原创作品,转载请保留出处。 作者主页:sysin.or…

Cisco ISR 4000 Series IOS XE Release 17.16.1a ED

Cisco ISR 4000 Series IOS XE Release 17.16.1a EDCisco ISR 4000 Series IOS XE Release 17.16.1a ED 思科 4000 系列集成服务路由器 IOS XE 系统软件 请访问原文链接:https://sysin.org/blog/cisco-isr-4000/ 查看最新版。原创作品,转载请保留出处。 作者主页:sysin.org思…

卷积运算

对应位置数字相乘,求和。卷积核(或滤波器)的小窗口在输入数据上滑动,计算窗口覆盖区域的元素乘积之和,从而生成输出数据。 二维卷积 运算 1x7 + 2x6 + 3x5 + 4x4 = 50 1x6 + 2x2 + 3x4 + 4x2 = 30 彩色图像卷积运算; R 卷积运算 对应位置 乘积求和; G-green 绿色…

linux实现macos的timeMachine系统备份

在上一篇文章中,我们详细介绍了Btrfs文件系统的基本使用方法和核心原理。本文将重点讲解如何利用Btrfs的特性来实现系统备份功能。 实现原理其实很简单:Linux内核支持直接从Btrfs的子卷(subvolume)启动系统。基于这个特性,我们可以通过计划任务定期为系统根目录创建快照,…