网络流
网络流
定义:
- 点数 n,边数 m
- 源点 s,汇点 t
- 容量 w :允许通过的最大流
- 流量 c :当前通过的流的大小
- 流 f :从汇点流出的流量
- 增广路 :从源点到汇点,边上剩余容量均为正的路径
- 反悔边 :容量为 0,流量为原边的 -c,代表可退回的流量
Edmonds_Karp
朴素 bfs 找增广路,记录路径
时间复杂度 \(O(nm^2)\)
代码:
namespace Edmonds_Karp{
int lst[N],f[N];
queue<int> q;
bool bfs(){
memset(lst,-1,sizeof(lst));
while(!q.empty()) q.pop();
q.push(s);
f[s]=INF;
while(!q.empty()){
int u=q.front();q.pop();
if(u==t) break;
REPG(i,g,u){
int v=g.edge[i].to,w=g.edge[i].w,c=g.edge[i].c;
if(lst[v]==-1 && w>c){
lst[v]=i;
f[v]=min(w-c,f[u]);
q.push(v);
}
}
}
return lst[t]!=-1;
}
LL solve(){
LL ans=0;
while(bfs()){
ans+=f[t];
for(int v=t;v!=s;v=g.edge[lst[v]^1].to){
g.edge[lst[v]].c+=f[t];
g.edge[lst[v]^1].c-=f[t];
}
}
return ans;
}
}
Dinic
bfs 分层图,在新图上 dfs 找增广路
dfs 需要当前弧优化(扔掉已经增广完的出边)
理论复杂度上界 \(O(n^2m)\)
单位容量图 \(O(m \min\{n^\frac{2}{3},m^\frac{1}{2}\})\)
代码:
namespace Dinic{
int dep[N],cur[N];
queue<int> q;
bool bfs(){
while(!q.empty()) q.pop();
memset(dep,-1,sizeof(dep));
memcpy(cur,g.head,sizeof(g.head));
q.push(s);dep[s]=1;
while(!q.empty()){
int u=q.front();q.pop();
if(u==t) break;
REPG(i,g,u){
int v=g.edge[i].to,w=g.edge[i].w,c=g.edge[i].c;
if(dep[v]==-1 && w>c){
dep[v]=dep[u]+1;
q.push(v);
}
}
}
return dep[t]!=-1;
}
int dfs(int u,int f){
if(u==t || !f) return f;
int res=0;
for(int i=cur[u];(~i) && res!=f;i=g.edge[i].nxt){
cur[u]=i;
int v=g.edge[i].to,w=g.edge[i].w,c=g.edge[i].c;
if(dep[v]==dep[u]+1 && w>c){
int nw=dfs(v,min(f-res,w-c));
res+=nw;
g.edge[i].c+=nw;
g.edge[i^1].c-=nw;
}
}
return res;
}
LL solve(){
LL ans=0;
while(bfs()) ans+=dfs(s,INF);
return ans;
}
}
最大流最小割定理
- 最大流 \(\ge\) 最小割
如果小于,则说明没有满流,矛盾 - 最大流 \(\le\) 最小割
如果大于,则说明 s 可以不通过割边到达 t,矛盾
综上,最大流 = 最小割
推论:对于每个最小割方案,存在最大流方案,其中每条割边都满流
最大权闭合子图
考虑最优为选所有正的,不选负的。
建图:
- \(s\) 为源点,\(t\) 为汇点。
- 对于依赖关系 \(x\to y\),连边 \((x,y,INF)\)。
- 对于节点,若代价为正,连边 \((s,i,v_i)\);反之,连边 \((i,t,-v_i)\)。
若割掉与 \(s\) 相连的边,代表不选这个正的,损失 \(v_i\) 贡献;若割掉与 \(t\) 相连的边,代表选这个负的,付出 \(v_i\) 代价。
发现 \(s\) 和 \(t\) 只会通过形如 \(s\to x\to y\to t\) 的边相连。
- 如果保留 \((y,t)\),即不选 \(y\),则必然割掉 \((s,x)\),即不选 \(x\)。
- 如果割掉 \((y,t)\),即选 \(y\),\((s,x)\) 可割可不割,不受影响。
故此图的割满足选 \(y\) 是选 \(x\) 的必要条件。
最大权为 所有正权值之和 \(-\) 最小割。
费用流
全称 最小费用最大流
每次找费用最小的增广路(bfs 改成 spfa)
基于 EK 的实现:
namespace EK{
LL f[N];
LL dis[N];
int lst[N];
bool vis[N];
queue<int> q;
bool SPFA(){
while(!q.empty()) q.pop();
memset(vis,0,sizeof(vis));
rep(i,1,n) dis[i]=INF;
rep(i,1,n) lst[i]=-1;
dis[s]=0;q.push(s);vis[s]=1;
f[s]=INF;
while(!q.empty()){
int u=q.front();q.pop();
vis[u]=0;
REPG(i,g,u){
int v=g.edge[i].to,w=g.edge[i].w,fl=g.edge[i].f,c=g.edge[i].c;
if(w>fl && dis[v]>dis[u]+c){
lst[v]=i;
f[v]=min(f[u],(LL)w-fl);
dis[v]=dis[u]+c;
if(!vis[v]) vis[v]=1,q.push(v);
}
}
}
return lst[t]!=-1;
}
void solve(){
LL mxf=0,mnc=0;
while(SPFA()){
for(int i=lst[t];~i;i=lst[g.edge[i^1].to]){
g.edge[i].f+=f[t];
g.edge[i^1].f-=f[t];
}
mxf+=f[t];
mnc+=f[t]*dis[t];
}
printf("%lld %lld\n",mxf,mnc);
}
}
基于 Dinic 的实现:
namespace Dinic{
LL dis[N],INF;
int cur[N];
bool inq[N];
queue<LL> q;
bool SPFA(){
memset(dis,0x3f,sizeof(dis));INF=dis[0];
memset(inq,0,sizeof(inq));
memcpy(cur,g.head,sizeof(g.head));
while(!q.empty()) q.pop();
dis[s]=0,inq[s]=1,q.push(s);
while(!q.empty()){
LL u=q.front();q.pop();
inq[u]=0;
REPG(i,g,u){
LL v=g.edge[i].to,w=g.edge[i].w,f=g.edge[i].f,c=g.edge[i].c;
if(dis[v]>dis[u]+c && w>f){
dis[v]=dis[u]+c;
if(!inq[v]) q.push(v),inq[v]=1;
}
}
}
return dis[t]<INF;
}
LL dfs(LL u,LL flow){
if(u==t || !flow) return flow;
LL res=0;inq[u]=1;
for(LL i=cur[u];(~i) && flow!=res;i=g.edge[i].nxt){
LL v=g.edge[i].to,w=g.edge[i].w,f=g.edge[i].f,c=g.edge[i].c;
cur[u]=i;
if(!inq[v] &&(dis[v]==dis[u]+c) && w>f){
LL nf=dfs(v,min(flow-res,w-f));
res+=nf;
g.edge[i].f+=nf;
g.edge[i^1].f-=nf;
}
}
inq[u]=0;
return res;
}
void solve(){
LL flow=0,cost=0;
while(SPFA()){
LL nf=dfs(s,INF);
flow+=nf;
cost+=nf*dis[t];
}
printf("%lld %lld\n",flow,cost);
}
}
技巧
- 拆点
- 流量限制
P2472 [SCOI2007] 蜥蜴 - 贡献不重复
P2045 方格取数加强版
- 流量限制
- 点数 & 边数优化
- 中转点(优化建边)
P2065 [TJOI2011] 卡片 - 只考虑询问相关(离散化思想)
P4452 [国家集训队] 航班安排 - 动态开点
P2050 [NOI2012] 美食节
- 中转点(优化建边)
- 分层图
- “时间”
P2754 [CTSC1999] 家园 / 星际转移问题 - dp 转移
P2766 最长不下降子序列问题
- “时间”
- 二分图相关
- 二分图最大匹配
- DAG 最小路径覆盖
P2765 魔术球问题
- 残量网络性质
P4126 [AHOI2009] 最小割 - 最大权闭合子图
P3749 [六省联考 2017] 寿司餐厅
P2805 [NOI2009] 植物大战僵尸 - 退流
P5295 [北京省选集训2019] 图的难题
P3308 [SDOI2014] LIS
习题
P2065 [TJOI2011] 卡片
题意:
两行数,每次选上下两个不互质的数,问最多选几次
数据范围 \(T\le 100,n,m\le 500\)
建图:
- 直接把不互质都连起来 \(O(n^2)\),但是会 T(尽管看起来复杂度是 \(O(Tn^3)\) )
key:无中生有质因子点
- 认为不互质的数是通过公共质因子相连的,这样边数是 \(O(n)\) 的,复杂度 \(O(Tn^2)\)
不能重复选 \(\to\) 所有边容量都是 1
P2763 试题库问题
题意:
n 个题,k 种属性,每个题可能有多种属性,要求每种属性有 \(x_i\) 道题。求方案
数据范围 \(k\le 20, n\le 10^3\)
建图:
每种属性一个点,把题和属性连起来
近似单位流量,复杂度大约 \(O(n^2k)\)
输出方案:
看每个属性的出边,哪些流量是 1
P2472 [SCOI2007] 蜥蜴
题意:
r 行 c 列网格,有一些蜥蜴,每次跳到欧几里得距离不超过 d 的节点,每个节点只能被经过 \(h_{i,j}\) 次,问最多可以有多少跳出边界
数据范围 \(r,c\le20,d\le4,h\le3\)
建图:
key:拆点-->限制经过次数
每个点拆成入点和出点,每条边是 (u出,v入)
有蜥蜴的入连源点,跳出边界的出连汇点
P2754 [CTSC1999] 家园 / 星际转移问题
k 个人从地球到月球,有 n 个中转站,m 条环形航线,每个时刻走到下个中转站。中转站容量无穷大,航线容量 \(h_i\)。问最早在什么时候全部到月球。
数据范围 \(n\le 13,m\le 20,k\le 50\)
key:按时间分层
注意细节/kk
- 每个时刻在残量网络上跑 Dinic,应该是
res+=solve() - 0 时刻不能建中转站之间的边
P2765 魔术球问题
n 根柱子,依次放编号 \(1,2,3,\dots\) 的球
- 每次只能在最上面放
- 任意两个相邻球编号和为完全平方数
问最多能放多少个球
数据范围 \(n\le 55\)
转化为:k 个球,最少要用几根柱子。
能相邻的连一条边,得到 DAG。
key:DAG 最小路径覆盖=原图点数-二分图最大匹配
证明:每找到一条匹配边,就相当于合并两条路径
二分图最大匹配可以用网络流(s 连左,t 连右,单位容量)
P2766 最长不下降子序列问题
给定序列 \(a\),求:
- 最长不下降子序列长度 \(k\)
- 每个元素只用一次,最多选出多少长为 \(k\) 的不下降子序列
- 不限制 \(a_1\) 和 \(a_n\),最多选出多少不同的
key:按 \(f_i\) 分层
注意特判 \(n=1\) 和 \(k=1\) /kk
P2057 [SHOI2007] 善意的投票 / [JLOI2010] 冠军调查
n 个人,每人一种立场(0/1),m 对朋友。
求:投票与自己立场不同 + 一对朋友投票不同 最小值
数据范围 \(n\le 300,m\le \frac{n(n-1)}{2}\)
建图:
s,t 分别连立场 0/1,朋友间连双向边。
去掉矛盾 \(\to\) 删边使 s,t 不连通
求最小割即可。
P2598 [ZJOI2009] 狼和羊的故事
\(n\times m\) 方格上有狼、羊、无人区,要修篱笆把它们分开,求最短长度。
数据范围 \(n,m\le 100\)
建图:\(s\to\) 羊 \(\to\) 无人区 \(\to\) 狼 \(\to t\)
求最小割。
key:无人区不需要连 s,t
原因:狼和羊被割开,相当于在中间修篱笆了,至于到底在无人区的哪里,并不关心。
P2774 方格取数问题
方格图,相邻的不能选,求最大价值和。
发现黑白染色后,同色一定不互斥,故互斥连边是二分图。
建图:\(s\to 黑 \to 白 \to t\),求最小割。
P4126 [AHOI2009] 最小割
给定 n 个点,m 条边的有向图,给定 s,t,对于每条边,询问:
- 是否可能在最小割中
- 是否一定在最小割中
key:残量网络 DAG
对于一条满流边 (u,v),如果残量网络上存在 u 到 v 的一条路径,显然可以破坏掉这条满流边,故这条边一定不在最小割中。
在残量网络上缩点,可行要求scc[u]!=scc[v],必须要求scc[u]==scc[s] && scc[v]==scc[t]。
P5039 [SHOI2010] 最小生成树
给定 \(n\) 点 \(m\) 边的边带权连通无向图,可以进行操作:
- 选择一条边,把其他边的权值 \(-1\)
求使得边 \((A,B)\) 一定出现在最小生成树中,需要的最少操作次数。
转化操作:其他边 \(-1\) 就是自己 \(+1\)
key:一定出现 \(\to\) \(w\le W\) 的边不能让 \(A B\) 连通
只保留边权小于等于 \(W(A,B)\) 的边,跑最小割即可。
P3749 [六省联考 2017] 寿司餐厅
题面很长,故不写了。
key: 最大权闭合子图
- 选一个区间,必选所有子区间(单向依赖)。
- 贡献 & 代价都只算一次。
- 有正有负。
打点补丁修正即可
P2805 [NOI2009] 植物大战僵尸
第一道独立完成的网络流紫题!Congratulations!
key:拓扑 + 最大权闭合子图
后者是显然的(只能从右边进入 & 攻击)
但环 & 环上挂的树都不能走到,即为拓扑剩下的点
注意:拓扑方向是(先走 \(\to\) 后走),与网络流相反!
P4452 [国家集训队] 航班安排
n 个点,m 个请求,在 s 时刻从 a 出发,t 时刻到 b。初始 K 个飞机在起点,要求 T 时刻全回来。(任意点间)飞行有时间和代价,请求有收益。问最大收益。
数据范围:\(n,m\le 200,T\le 3000\)
错误思路:按时间分层建图(点数过多)
key:只考虑请求相关的点
在 x 时刻前能到达就连边
注意起点要拆,因为有 K 架飞机。
P2045 方格取数加强版
PS:此方格取数非彼方格取数(bushi
\(n\times n\) 矩阵,从 (1,1) 向右 / 下走到 (n,n)。每个格子贡献不重复计算,一共走 K 次,求最大和。
数据范围:\(n\le 50\)
key:贡献不重复 \(\to\) 拆点连双边
入 \(\to\) 出建两条边 (1,val),(INF,0)
P2050 [NOI2012] 美食节
是 P2053 [SCOI2007] 修车 的数据加强版。
n 种菜(每种点 \(p_i\) 份),m 个厨师。每个厨师做菜时间不同,求最小总等待时间。
做每道菜贡献的等待时间,与这是第几道菜有关。拆点,厨师 i 做的倒数 第 k 道菜,等待时间要 \(\times k\),代表有 \(k\) 个人要等待。
如果直接建图就是 P2053 [SCOI2007] 修车,在本题会拿到 \(60\) pts 的好成绩。
key:动态开点
发现有很多点是不会被用到的,故只需在第 k 个点被用到之后建第 k+1 个点,判断平凡
点数 \(O(n+p)\) 边数 \(O(np)\),可以通过本题。
P2604 [ZJOI2010] 网络扩容
有向图,容量 c,扩容费用 w。求:
- 不扩容最大流
- 将最大流增加 k 的最小扩容费用。
跑一遍最大流,然后给每条边对应再建一条 (INF,c) 的边,跑费用流即可。
P2770 航空路线问题
已知某些城市间有航线,求一条途径城市最多的路线满足:
- 从最西端城市出发,单向从西到东到达最东端,再从东到西回去。
- 除起点外,任何城市只能经过一次
输出最大通过数及方案
拆点,起终点容量为 2,其他为 1,费用均为 1,最大费用最大流。
trick:两点间连边容量 inf(拆点已经限制过了,避免特判 (1,n))
输出方案平凡。
P5295 [北京省选集训2019] 图的难题
给定无向图,问能否染成两个颜色,分别内部无环(森林)。
数据范围 \(n\le 2000,m\le 4000\)
结论:可以染成当且仅当对于任意导出子图,\(|E|\le 2|V|-2\)
证明很长,与网络流无关,故略过。
转化条件,相当于要求 \(\max\{|E|-2|V|\}\le -2\)
显然是最大权闭合子图,选一条边必须选两个端点。
注意,要求子图非空,需要枚举一个必须选的点,在网络中删去这个点再跑最大流。
key: 退流
删去这个点本质就是将 \((u,T)\) 的容量置为 0。
若要删去边 \((u,v)\),则从 \(u\) 到 \(S\) 跑最大流,再从 \(T\) 到 \(v\) 跑最大流。
最后将容量置为 0,再重新跑 \(S\) 到 \(T\) 的最大流。本质:把流到 \((u,v)\) 边的流量通过反向边退回去,下次跑的时候换条路流。
感性理解,流到 \((u,v)\) 边的流量不会太多,退回去后跑 dinic 的复杂度也不会很高,所以是很有效的优化。

浙公网安备 33010602011771号