Network Flow 网络流
最大流
形式化定义
将最大流类比为“水流”是不大恰当的。事实上最大流问题有形式化定义:
- 对于每一条边 \((u,v)\),有给定的容量 \(c(u,v)\),你需要求出每条边的流量 \(f(u,v)\)。图上有互异两点 \(S,T\) 分别为源点汇点。
- 容量限制:对于每一条边 \((u,v)\),有 \(0\leq f(u,v)\leq c(u,v)\)。
- 流量守恒:对于每一个点 \(u(u\neq S,u\neq T)\),有 \(\sum_{(v,u)\in G} f(v, u)=\sum_{(u,v)\in G} f(u, v)\)。
定义最大流为 \(\max f(S)\)。
求解算法
最大流可以用 Dinic 算法在 \(O(n^2m)\) 的时间复杂度内解决。大致思路是对原图分层,然后 dfs 上找到一条合法的增广路径并且将其增广,然后在残量网络中退流。
bool bfs(){
queue<int> q;
fill(dep + 1, dep + n + 1, 0);
q.push(S);dep[S] = 1;
while(!q.empty()){
int u = q.front();
q.pop();
for(int i=head[u];i;i=g[i].nxt){
int v = g[i].to;
if(g[i].cap > 0 && !dep[v]) dep[v] = dep[u] + 1, q.push(v);
}
}
return dep[T];
}
int dfs(int u,int f){
if(u == T) return f;
for(int &i=cur[u];i;i=g[i].nxt){
int v=g[i].to;
if(!g[i].cap || dep[v] != dep[u] + 1) continue;
int flow = dfs(v, min(f, g[i].cap));
g[i].cap -= flow;g[i ^ 1].cap += flow;
if(flow) return flow;
}
return 0;
}
二分图(一)
二分图最大匹配:你需要在二分图中选出一些边,使得这些边两两不共端点。最大化边的数量。
二分图最大匹配可以使用匈牙利算法在 \(O(nm)\) 的时间复杂度内解决。思路是贪心地找到可以成为匹配的边。
bool solve(int s){
while(!q.empty()) q.pop();
q.push(s);
while(!q.empty()){
int u = q.front();
q.pop();
if(vis[u] == s) continue;
vis[u] = s;
for(int i=head[u];i;i=g[i].nxt){
int v = g[i].to;
if(!mch[v]){
mch[v] = u;
return true;
}
else q.push(mch[v]);
}
}
return false;
}
事实上根据二分图最大匹配的定义,我们可以使用最大流建模解决这个问题。具体来说,我们建立超级源点 \(S\) 和超级汇点 \(T\),对于左部点 \(u_i\),连边 \((S,u_i,1)\),对于右部点 \(v_i\),连边 \((v_i,T,1)\),对于原图上的边 \((u_i,v_i)\),连边 \((u_i,v_i,1)\)。答案便是最大流。时间复杂度为 \(O(n\sqrt{m})\)。
二分图最小点覆盖:你需要在二分图上选择若干个点,使得对于每一条边,都至少有一个端点被选择,最小化选择点的数量。
二分图最小边覆盖:你需要在二分图上选择若干条边,使得对于每一个点,都为有至少一条边的端点。最小化选择边的数量。
二分图最大独立集:你需要选择若干个个点,使得这些点两两没有边直接相连。最大化点的数量。
König 定理:二分图最大匹配 = 二分图最小点覆盖 = n - 二分图最小边覆盖 = n - 二分图最大独立集。
最小割
最小割:给你一个图,每条边有容量。你需要删除若干条边,使得 \(S\to T\) 不连通。最小化删去边的容量和。
最大流最小割定理:最大流 = 最小割。
UVA11380 Down Went The Titanic
多组数据。每组数据给出一个 \(n\times m\) 的地图 \(s\) 和一个 \(p\)。
- 为
*的格子(人与浮冰)表示这里有一个人,人离开后,这个格子会变成~,只能经过一次。- 为
~的格子(水流)表示不可通行。- 为
.的格子(浮冰)表示只能通过一次。- 为
@的格子(厚冰)表示可以通过任意次。- 为
#的格子(木板)表示可以通过任意次,且可以停留至多 \(p\) 个人。每个人可以上下左右移动,求最多多少个人可以在
#上停留。
网络流大模拟。
首先我们建立源点 \(S\),汇点 \(T\)。将每个点拆成入点 \(A_{i,j}\) 出点 \(B_{i,j}\) 方便在点上加限制。我们将流量看成是人的流动,然后开始网络流建模。
对于人与浮冰的位置 \((i,j)\),连边 \((S,A_{i,j},1),(A_{i,j},B_{i,j},1)\)。表示会有一个人在这个位置,且只能经过一次。
对于水流位置,我们不做处理,表示断开。
对于浮冰的位置 \((i,j)\) 连边 \((A_{i,j},B_{i,j},1)\) 表示只能经过一次。
对于厚冰的位置 \((i,j)\),连边 \((A_{i,j},B_{i,j},+\infty)\) 表示可以经过任意次。
对于木板的位置 \((i,j)\),连边 \((A_{i,j},B_{i,j},+\infty),(B_{i,j},T,p)\) 表示可以经过任意次,且只能至多停留(可以理解成离开)\(p\) 个人。
然后连边 \((B_{i,j},A_{i+1,j},+\infty),(B_{i,j},A_{i-1,j},+\infty),(B_{i,j},A_{i,j+1},+\infty),(B_{i,j},A_{i,j-1},+\infty)\) 表示上下左右移动。
然后就做完了。
CF387D George and Interesting Graph
给你一个有向图,你可以删去或添加边,使得没有重边,且存在一个中心 \(u\),使得有对于每个点 \(v\),有边 \((u,v),(v,u)\),且除中心外每个点入度和出度均为 \(2\)。最少话添加删除总数。
枚举中心,然后删除其他边,添加一定的边。满足中心点限制之后发现度数只剩下 \(1\),且我们一定要尽量复用之前的边。我们直接将每个点拆成入点和出点,二分图最大匹配即可。
P4126 [AHOI2009] 最小割
给出一个 \(n\) 个点 \(m\) 条边的有向图,源点为 \(S\),汇点为 \(T\) 对于每一条边,询问是否分别满足以下条件:1. 是否所有合法的最小割集均有这条边。2. 是否存在一个合法的最小割集有这条边。
先对这个图跑一遍最大流,则可能在最小割集中的边一定是满流的边。为什么?反证法。假如一条最小割集中的边不满流,则我们一定可以找到另一条路径,使得存在一条边更小,则我们断掉这一条边一定更优。注意这个条件必要但不充分。
然后我们去掉所有可能成为答案的边,也就是原图的残量网络。如果残量网络两端点在同一个强连通分量中,则这条边无意义,不是答案。否则可以是答案。如果一条边两顶点分别在 \(S,T\) 所在强连通分量中,则这一定要被割掉。是必要的。
P4313 文理分科
给出一个矩阵。将一个点染成白色或黑色有一定的收益,一个点与其相邻的若干个点颜色相同会有一定的收益。最大化收益和。
我们考虑用最小割模型来做。我们加上收益,等价于割掉了边。
则我们钦定 \(S\) 为白,\(T\) 为黑。然后对于每个点,分别连接,容量为产生的贡献。对于相邻贡献,我们新建一个点,连向相邻的边,容量无穷大(不会被割掉),然后分别连接 \(S,T\)。最后跑一遍最大流即可。
费用流
形式化定义
在最大流基础上,所有边增加一个费用,在最大流量基础上最小化每条边流量乘以费用的和。
求解算法
将 Dinic 中的 BFS 换成死了的 spfa,可以做到 \(O(nmf)\) 的复杂度。\(f\) 为增广次数。
bool spfa(){
memset(dis,0x3f,sizeof(dis));
int infty=dis[0];
queue<int> q;
q.push(S);dis[S]=0;
while(!q.empty()){
int u=q.front();
q.pop();vis[u]=0;
for(int i=head[u];i;i=g[i].nxt){
int v=g[i].to;
if(g[i].cap&&dis[u]+g[i].cost<dis[v]){
dis[v]=dis[u]+g[i].cost;
if(!vis[v]){
q.push(v);
vis[v]=1;
}
}
}
}
return dis[T]!=infty;
}
int dinic(int u,int flow){
if(u==T) return flow;
vis[u]=1;int ret=0;
for(int i=cur[u];i&&ret<flow;i=g[i].nxt){
int v=g[i].to;
if(!vis[v]&&g[i].cap&&dis[v]==dis[u]+g[i].cost){
int tmp=dinic(v,min(g[i].cap,flow-ret));
if(tmp){
mincost+=tmp*g[i].cost;ret+=tmp;
g[i].cap-=tmp;g[i^1].cap+=tmp;
}
}
}
vis[u]=0;
return ret;
}
void MCMF(){
while(spfa()){
memcpy(cur,head,sizeof(cur));
int x;;
while((x=dinic(S,0x3f3f3f3f))) {
maxflow+=x;
}
}
}
二分图(二)
二分图最大权匹配:你需要在二分图中选出一些边,使得这些边两两不共端点。最大化边权和。
考虑使用费用流模型,建立超级源点,超级汇点,仿照求最大匹配的方法连边,在原图中的边加上费用即可。
P3705 [SDOI2017] 新生舞会
有一个二分图,第 \(i\) 条边有两个边权 \(a,b\)。你需要选出一个匹配,使得边权 \(a\) 的和与 \(b\) 的和的比尽可能大。输出这个最大值。
2017 年还出这种板子题吗?
标准的 01 分数规划问题。我们二分这个答案 \(C\),则相当于每条边边权为 \(a_i-b_iC\) 的二分图最大权匹配是否大于等于 \(0\)。费用流即可。
P3965 [TJOI2013] 循环格
给出一个 \(R\times C\) 的矩阵。每个格子上写着一个方向(LRUD)。你需要修改一些方向,使得从每个点开始都可以按照方向回到这个点(注意如果出界,则回到另一侧)。最小化修改的数量。
容易发现如果建成图,则满足条件的图由若干个简单环构成,就是入度出度都为 \(1\)。因此我们可以将每个点拆成入点和出点,源点向入点连容量为 \(1\) 的边,出点向入点连容量为 \(1\) 的边。其他连可以被改成的和原来的边,改成的费用为 \(1\),原来的费用为 \(0\)。费用流即可。
P2053 [SCOI2007] 修车
有 \(n\) 个车主,每个车主一辆车。有 \(m\) 个修车师傅。不同的修车师傅修理不同的车耗时不同。你需要安排修车策略,最小化每个车主等待时间平均值。
考虑将每个修车师傅拆成若干个点,表示修车师傅修倒数第几个车,用这个去选车,相当于一个二分图带权最大匹配,费用流即可。
P2050 [NOI2012] 美食节
咕咕了。
P5458 [BJOI2016] 水晶
咕咕了。
上下界网络流
无源汇上下界可行流
给你一个 \(n\) 个点 \(m\) 条边的图,每条边有容量上下界。求是否存在每条边的流量分配方案,使得每个点流量平衡。
考虑对于每一条边的上下界为 \([l,r]\)。则我们先将这条边的容量设置为 \(r-l\)。然后这样最后的可行流加上 \(l\) 即可。
对吗?显然是不对的。原因是一个点的入度和出度不一定相同,则每个点可能流量不平衡。
对于一个点,我们记录以下这个点应该加上的入流量为 \(I_i\),出流量为 \(O_i\)。
则如果 \(I_i=O_i\),则无需操作。
如果 \(I_i>O_i\),说明需要多取 \(I_i-O_i\) 的流量。则我们建立一个源点 \(S\),连边 \((S,i,I_i-O_i)\)。
否则说明需要多流出 \(O_i-I_i\) 的流量。则我们建立一个汇点 \(T\),连边 \((T,i,O_i-I_i)\)。
然后跑 \(S\to T\) 的最大流。如果存在 \(S\) 的出边不能满流,则存在不满足限制的边,判定为无可行流。否则即为有可行流。
有源汇上下界可行流
给你一个 \(n\) 个点 \(m\) 条边的图,每条边有容量上下界。求是否存在每条边的流量分配方案,使得除源点 \(S\) 和汇点 \(T\) 外,每个点流量平衡。
考虑在无源汇上下界可行流的基础上,在原图中连边 \((T,S,0,+\infty)\) 来规避。有解则 \(S\to T\) 可行流流量等于这条边的流量。
有源汇上下界最大流
给你一个 \(n\) 个点 \(m\) 条边的图,每条边有容量上下界。求是否存在每条边的流量分配方案,使得除源点 \(S\) 和汇点 \(T\) 外,每个点流量平衡。在流量平衡的情况下,最大化 \(S\) 的出流量。
在有源汇上下界可行流的基础上,我们删去 \(T\to S\) 这条边,跑一遍最大流。榨干残量网络的剩余价值。由于下界已经满足,上界由于容量限制也会满足,所以这个方法是正确的。
有源汇有上下界最小流
给你一个 \(n\) 个点 \(m\) 条边的图,每条边有容量上下界。求是否存在每条边的流量分配方案,使得除源点 \(S\) 和汇点 \(T\) 外,每个点流量平衡。在流量平衡的情况下,最小化 \(S\) 的出流量。
在有源汇上下界可行流的基础上,我们删去 \(T\to S\) 这条边,\(T\to S\) 跑一遍最大流,这一部分流量可以退掉。
P4043 [AHOI2014/JSOI2014] 支线剧情
有一个 DAG,你需要从 \(1\) 号点开始遍历这个图,你不能回溯,到达一个点后你可以返回 \(1\) 点。你需要经过所有边(可以经过任意次)。每条边有一个权值,你需要最小化经过的所有边权值和。
相当于对每个点,向 \(1\) 连一条边,然后每条原始边有一个下界 \(1\),没有上界。无源汇上下界最小费用可行流即可。方法和普通的无源汇上下界可行流差不多。
P4311 士兵占领
有一个 \(M \times N\) 的棋盘,有的格子是障碍。现在你要选择一些格子来放置一些士兵,一个格子里最多可以放置一个士兵,障碍格里不能放置士兵。我们称这些士兵占领了整个棋盘当满足第 \(i\) 行至少放置了 \(L_i\) 个士兵,第 \(j\) 列至少放置了 \(C_j\) 个士兵。现在你的任务是要求使用最少个数的士兵来占领整个棋盘。
考虑建立一个二分图,一边为行,一边为列。将 \(S\) 向行,列向 \(T\) 的加上流量下界。然后就是一个类似二分图匹配的问题。有源汇上下界最小费用最小流即可。
P4553 80人环游世界
有一个无向图,你需要用 \(m\) 条路径覆盖这个图,使得每个点被覆盖了恰好 \(v_i\) 次。最小化覆盖的边边权和。
将每个点拆成入点和出点,然后建一个类似二分图的结构,对 S 向入点,出点向 T 的边上加上下界限制表示覆盖的次数。另外需要注意要限制总流量,我们将源点拆成两个点,中间连一条上下界均为 \(m\) 的边即可。最后跑一遍有源汇上下界最小费用可行流即可。
NP-Hard 问题被解决了吗?
【哈密顿路径问题(经典 NP-Hard 问题)】给定一个有向图,你需要找到一个路径,使得不重不漏地经过所有点。
有一个非常 naive 的方法。我们对于每一个点,拆成入点出点,中间连一条上下界均为 \(1\) 的边。原来图的边钦定上界为 \(1\),下界为 \(0\)。然后枚举源点汇点,跑一遍有源汇上下界最小费用可行流即可。
对吗?不对。为什么呢?因为我们回到网络最大流的原始定义。最大流根本不存在经过一条边,容量 -1 的说法,完全是基于流量平衡的东西。这玩意在有环的图上会失效。
所以 NP-Hard 问题没有被这种方法解决。

浙公网安备 33010602011771号