网络流
网络流
模型框架
现实模型:自然水供水系统。它相当于水管调度员。
- 问题的本质是解决有向图中“资源分配与路径优化”的问题。
定义
两个特殊节点:源点(\(S\))、汇点(\(T\))。
边的关键属性:容量 \(c\)。
- 每条有向边都有一个非负实数“容量”。
性质
- “流”作为资源的实际分配,必须满足两个核心规则,否则不是合法的网络流。
- 容量约束:任意一条边上的实际流量 (\(f\)),不能超过该边的容量(\(c\)),即 \(f \le c\)。例如,一根容量为 10 t 的水管,实际输水不能超过 10 t。
- 流量守恒:除了源点和汇点,其他所有中转节点的“流入流量总和”必须等于“输出流量总和”。
- 反对称性约束(斜对称):对任意 \(u,v \in V\),\(f(u,v) = -f(v,u)\)。
- 弧流量约束(容量限制):\(0 \le f(u,v) \le c(u,v)\)。
最大流定义:对于网络 \(G=(V,E)\),给每条边指定流量,得到合适的流 \(f\),使得 \(f\) 的流量尽可能大。此时我们称 \(f\) 是 \(G\) 的最大流。
最大流求法
- 最大流德核心遵循 Ford-Fulkerson 框架:不断在“残留网络”中找“增广路”(能从源点流向汇点且有剩余容量的路径)。
- EK 算法适用于节点数 \(\le 1000\)、边数 \(\le 10^4\) 的小规模网络,时间复杂度 \(O(F \times E)\)(\(F\) 是最大流,\(E\) 是边数),流量大时效率低。
Dinic 算法:
- 核心思路
在 Ford-Fulkerson 框架上做两个优化:
- 分层(BFS):先给节点按“到源点的最短距离”分层,保证增广路是“最短路径”;
- 多路增广(DFS):在分层图中用 DFS 批量找增广路,且用“当前弧优化”避免重复遍历有效边。
- 大体流程为:
- 用 bfs 求出 G 中每个点的深度。
- 用 dfs 在 bfs 所跑出来的途中,枚举一条从源点到汇点的路径 P,求出 \(limit = \min{(c \in P)}\),将路径上的每条边都减去 \(limit\),并新建一条反边,权值为 \(limit\)。
- 当前弧优化
这里的“弧”指的就是一个点 \(x\) 向其它点连的有向出边。
当我们选择一个点 \(x\) 的第 \(i\) 条弧作为一条增广路径的一部分时,前 \(i-1\) 条边一定已经能流的都流了,所以考虑标记用到哪条弧,下次 dfs 就可以跳过这些弧,提升程序效率。
复杂度分析
注:不知道严不严谨。(来自 Alex_Wei 的好文)
引理 1
每次增广后的 \(S\) 到各个点的最短路不减。
证明
考虑每次加入新边的方向,因为是反向边,所以是从 \(dis\) 大的点指向 \(dis\) 小的点。
如果最短路长度减少,那么在新的最短路中(记为 \(dis'\))一定存在 \((x,y)\) 满足 \(dis_x +1 <dis_y\)。那么说明 \((x,y)\) 不在原残量网络中,否则不满足三角形不等式(\(dis_y \ge dis_x + w\),\(w\) 为 \((x,y)\) 的边权)。
由于 \((x,y)\) 是新加入的边,说明 \((y,x)\) 在上一轮被增广,而增广需要满足 \(dis_x =dis_y +1\)。
于是将 \(dis_x = dis_y +1\) 代入 \(dis_x + 1 <dis_y\),得到 \(dis_y +2 < dis_y\),矛盾,故引理 1 成立。
引理 2
Dinic 的每次增广都会使 \(S\) 到 \(T\) 的最短路增加。
证明
反证法。设 \(S\) 到 \(T\) 的最短路不增加。由引理 1 知 \(S\) 到 \(T\) 的最短路不变。
那么这一条最短路 \(P\) 一定不是原残量网络中的最短路(否则会被增广掉),于是必然存在一个 \((x,y)\),满足 \(dis_x + 1 < dis_y\)(三角不等式在原残量网络上恒成立的逆命题),由类似引理 1 的方法可以导出矛盾,故引理 2 成立。
于是可以得到增广次数是 \(O(n)\) 的,因为每次增广都会让增广路上剩余流量最小的边满流(相当于这条边在新图上被删了。这里并不是真正的删掉了,只是因为容量为 \(0\) 被跳过不访问,相当于删掉)。我们把这种边称为关键边,一条关键边可以进行一轮增广,所以增广轮数是 \(O(m)\) 的,增广路的长度是 \(O(n)\)。
故 Dinic 的时间复杂度为 \(O(n^2m)\),但是这个上界非常松,这也是为什么 Dinic 算法在求网络最大流时表现良好。
模板代码
#include<bits/stdc++.h>
#define int long long
#define rep(i,l,r) for(int i=(l);i<=(r);++i)
using namespace std;
const int INF=1e9;
const int M=1e5+10,N=1e4+10;
int n,m,S,T,tot=1;
int head[N],del[N],dis[N];
struct edge{
int to,nxt,w;
}e[M<<2];
void add(int u,int v,int w){
++tot;
e[tot].w=w;
e[tot].to=v;
e[tot].nxt=head[u];
head[u]=tot;
}
bool bfs(){
rep(i,1,n){
dis[i]=INF;
}
queue<int> q;
dis[S]=0;
del[S]=head[S];
q.push(S);
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=head[x];i!=0;i=e[i].nxt){
int y=e[i].to,w=e[i].w;
if(dis[y]!=INF || w<=0)continue;
dis[y]=dis[x]+1;
del[y]=head[y];
q.push(y);
if(y==T)return 1;
}
}
return 0;
}
int dfs(int x,int limit){
if(x==T)return limit;
int flow=0,p;
for(int i=del[x];i!=0;i=e[i].nxt){
if(limit<=0)break;
del[x]=i;
int y=e[i].to,w=e[i].w;
if((dis[y]!=dis[x]+1) || w<=0)continue;
p=dfs(y,min(limit,w));
if(p==0){
dis[y]=INF;
continue;
}
e[i].w-=p;
e[i^1].w+=p;
flow+=p;
limit-=p;
}
return flow;
}
int dinic(){
int res=0;
while(bfs()){
res+=dfs(S,INF);
}
return res;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);cout.tie(nullptr);
cin>>n>>m>>S>>T;
while(m--){
int u,v,c;
cin>>u>>v>>c;
add(u,v,c);
add(v,u,0);
}
cout<<dinic()<<"\n";
return 0;
}
对网络最大流的理解
实际上是反悔贪心,我们如果在增广时选择了不是最优的路径去增广,我们可以通过反向边进行反悔。下面是来自 OI-wiki 的一张图:

最大流最小割定理
最大流最小割定理:网络的最大流量等于割的最小容量。
最大流最小割定理是贯穿网络流始终的核心。它是网络流相关理论的最基本结论。
-
任意可行流的流量不大于任意割的容量。
证明:考虑任何可行流和任意割 \((A,B)\)。所以:
\[|f| \le \sum\limits_{(x,y) \in (A,B)} f(x,y) \le \sum\limits_{(x,y)\in (A,B)} c(x,y) = c(A,B) \] -
对于整数容量网络,存在一个可行流的流量等于一个割的容量。
证明:考虑进行有限次增广后残量网络的结果。此时一定有 \(S,T\) 不联通,否则可以继续增广。
那么记 \(S\) 可达的集合为 \(A\),记 \(B= \{V-\{A\}\}\)。因为 \(A\) 在残量网络上不可达 \(B\),所以对于 \(B \rightarrow A\) 的边,它们都没有流量。同时 \(A \rightarrow B\) 的边都满流。
所以:
\[|f|= \sum\limits_{(x,y) \in (A,B)} f(x,y) = \sum\limits_{(x,y) \in (A,B)} c(x,y)= c(A,B) \]
根据上述证明,我们得到了两个集合之间的联系,流集合的每一个元素小等于割集合的每一个元素,且流集合中存在一个数,使其等于割集合。所以最大流等于最小割。
最小割
割
对于一个网络流图 \(G=(V,E)\),其割的定义为一种点的划分方式:将所有的点划分为 \(A,B=V-A\) 两个集合,其中源点 \(s \in A\),汇点 \(t \in B\)。
割的容量
我们定义割 \((A,B)\) 的容量 \(c(A,B)\) 表示所有从 \(A\) 到 \(B\) 的边的容量之和,即 \(c(A,B)= \sum_{u \in A,v \in B} c(u,v)\)。当然我们也可以用 \(c(S,T)\) 表示 \(c(A,B)\)。
最小割
最小割就是求得一个割 \((A,B)\) 使得割的容量 \(c(A,B)\) 最小。
求法
根据最大流最小割定理,我们直接对图 \(G\) 跑一遍最大流即可。
方案
一个误区:满流的边就是割边。这个思路乍一看很对,因为满流的边说明了这条边不会出现在残量网络中。
反例:\(c(S,1) = c(x,T) = 1\)。
回忆割的定义:
对于一个网络流图 \(G=(V,E)\),其割的定义为一种点的划分方式:将所有的点划分为 \(A,B=V-A\) 两个集合,其中源点 \(s \in A\),汇点 \(t \in B\)。
在证明最大流最小割的定理时,我们很自然地说明了对于 \(S\) 到达的点,它们是属于 \(A\) 集合(包含源点 \(S\) 的那个集合),剩下的 \(V-A\) 自然就是 \(B\) 集合了。
费用流
- 费用流是“带成本的网络流”。
- 即在最大流的基础上,新增单位费用。
注意:这里不讲最大费用最大流,其本质和最小费用最大流是一致的。
最小费用最大流的定义(来自 P7173 题面描述):
给出一个包含 \(n\) 个点和 \(m\) 条边的有向图(下面称其为网络) \(G=(V,E)\),该网络上所有点分别编号为 \(1 \sim n\),所有边分别编号为 \(1\sim m\),其中该网络的源点为 \(s\),汇点为 \(t\),网络上的每条边 \((u,v)\) 都有一个流量限制 \(w(u,v)\) 和单位流量的费用 \(c(u,v)\)。
你需要给每条边 \((u,v)\) 确定一个流量 \(f(u,v)\),要求:
- \(0 \leq f(u,v) \leq w(u,v)\)(每条边的流量不超过其流量限制);
- \(\forall p \in \{V \setminus \{s,t\}\}\),\(\sum_{(i,p) \in E}f(i,p)=\sum_{(p,i)\in E}f(p,i)\)(除了源点和汇点外,其他各点流入的流量和流出的流量相等);
- \(\sum_{(s,i)\in E}f(s,i)=\sum_{(i,t)\in E}f(i,t)\)(源点流出的流量等于汇点流入的流量)。
定义网络 \(G\) 的流量 \(F(G)=\sum_{(s,i)\in E}f(s,i)\),网络 \(G\) 的费用 \(C(G)=\sum_{(i,j)\in E} f(i,j) \times c(i,j)\)。
你需要求出该网络的最小费用最大流,即在 \(F(G)\) 最大的前提下,使 \(C(G)\) 最小。
人话:在保证最大流被求出的同时,让费用最小,费用就是流量 \(\times\) 单位费用。
算法流程
每次用 spfa 找到残量网络上费用最小的增广路进行增广,不能增广时就求出了最大流以及最小费用。
这个流程本质是 EK 算法。只不过由 bfs 找无权边增广路改为了 spfa 找费用最小的增广路。
正确性证明
不会,留空。
多源汇最大流
顾名思义,就是给出若干个源点 \(s_i\) 和若干个汇点 \(t_i\),求网络的最大流。
- 考虑图论建模,建出一张新图 \(G'\),其中在 \(G'\) 的源点 \(S\) 向原图 \(G\) 中的每个源点连容量为 INF 的边,每个汇点 \(t_i\) 向 \(G'\) 的汇点 \(T\) 连容量为 INF 的边。此时新图的最大流等于原图的最大流。
Proof
证明思想:考虑证明:
- 对于 \(G'\) 中的每一条可行流,都对应着 \(G'\) 中的一条可行流。
- 对于 \(G\) 中的每一条可行流,都对应着 \(G'\) 中的一条可行流。
- 于是得到 \(G\) 和 \(G'\) 中的可行流一一对应,自然就有原图的最大流等于新图的最大流。
-
对于 \(G'\) 中的一条可行流,因为除了 \(S,T\) 之外的点都满足流量守恒和容量限制,所以一定对应原图中的一条可行流。
-
对于 \(G\) 中的每一条可行流进行考虑。对于 \(G\) 中的点 \(v\) 满足 \(v \in \{ V- \{s_i,t_i \} \}\),都满足容量限制和流量守恒。在 \(G'\) 中,\(S\) 和 \(T\) 补偿了原图的源点汇点没有满足流量守恒的情况。(从 \(S\) 流出的容量为 INF 的边补偿了 \(s_i\) 的入边流量,\(T\) 同理),所以 \(G'\) 中的每一条可行流也对应着 \(G\) 中的一条可行流。
所以 \(G'\) 的可行流与 \(G\) 的可行流一一对应,由此可得,\(G\) 的最大流等于 \(G'\) 的最大流。
Code
#include<bits/stdc++.h>
#define int long long
#define rep(i,l,r) for(int i=(l);i<=(r);++i)
using namespace std;
const int INF=1e9;
const int M=2e5+10,N=5e4+10;
int n,m,S,T,tot=1,Sc,Tc;
int head[N],del[N],dis[N];
struct edge{
int to,nxt,w;
}e[M<<2];
void add(int u,int v,int w){
++tot;
e[tot].w=w;
e[tot].to=v;
e[tot].nxt=head[u];
head[u]=tot;
}
bool bfs(){
rep(i,0,n+1){
dis[i]=INF;
}
queue<int> q;
dis[S]=0;
del[S]=head[S];
q.push(S);
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=head[x];i!=0;i=e[i].nxt){
int y=e[i].to,w=e[i].w;
if(dis[y]!=INF || w<=0)continue;
dis[y]=dis[x]+1;
del[y]=head[y];
q.push(y);
if(y==T)return 1;
}
}
return 0;
}
int dfs(int x,int limit){
if(x==T)return limit;
int flow=0,p;
for(int i=del[x];i!=0;i=e[i].nxt){
if(limit<=0)break;
del[x]=i;
int y=e[i].to,w=e[i].w;
if((dis[y]!=dis[x]+1) || w<=0)continue;
p=dfs(y,min(limit,w));
if(p==0){
dis[y]=INF;
continue;
}
e[i].w-=p;
e[i^1].w+=p;
flow+=p;
limit-=p;
}
return flow;
}
int dinic(){
int res=0;
while(bfs()){
res+=dfs(S,INF);
}
return res;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);cout.tie(nullptr);
cin>>n>>m>>Sc>>Tc;
S=0,T=n+1;
while(Sc--){
int x;
cin>>x;
add(S,x,INF);
add(x,S,0);
}
while(Tc--){
int x;
cin>>x;
add(x,T,INF);
add(T,x,0);
}
while(m--){
int u,v,c;
cin>>u>>v>>c;u
add(u,v,c);
add(v,u,0);
}
cout<<dinic()<<"\n";
return 0;
}
上下界网络流
问题简述
- 上下界网络流是普通网络流的扩展。
- 边 \((u,v)\) 的流量必须满足 \(b(u,v) \le f(u,v) \le c(u,v)\)。即下界是 \(b(u,v)\),上界是 \(c(u,v)\)。
1. 无源汇上下界可行流
题面描述:
给你一个 \(n\) 个点、\(m\) 条有向边的有向图 \(G\),每条边有流量下界 \(l_i\) 和流量上界 \(r_i\)。
你需要判断是否存在一种方案使得每条边的流量 \(w_i\) 满足流量限制 \(l_i\leq w_i\leq r_i\),且每个点流量平衡,即每个点流入流量等于流出流量。
构造一组满足上述限制的流量方案或报告无解。
-
核心思路是先流满下界 \(l_i\)(操作后不一定流量守恒),然后不断调整,使其满足流量守恒。我们可将最后的可行流 \(f\) 看做是 \(f' + l_i\)。这里的 \(l_i\) 就是已经流满的下界。
-
记 \(d(u) = \text{从 u 流入的流量} - \text{从 u 流出的流量}\)(本质是净流量)。新建虚拟源汇点 \(SS,TT\)(与下文的有源汇上下界最大/小流的真实源汇点 \(S,T\) 区分)。
分类讨论:
- 若 \(d(u)>0\),那么从 \(u\) 流出的流量大于流入的流量,需要补充入流量,将 \(SS\) 连一条容量为 \(d(u)\) 至 \(u\)。
- 若 \(d(u)<0\),入流量大于出流量,那么需要补充出流量,故连边 \(u \rightarrow TT\),容量为 \(|d(u)|=-d(u)\)。
- 若 \(d(u) = 0\),说明这个点已经满足流量守恒,无需管它。
- 来自 Alex_Wei 的好文坑点提示:
注意
初学者容易搞反连边的方式。我们可能会认为,如果 \(w_i > 0\),即流到 \(i\) 的流量太多了,那应该连边 \(i\to TT\) 容量为 \(w_i\),把这些流量流走才对。原因是最终可行流的定义 \(f = b + f'\)。在 \(b\) 中 \(i\) 的净流量为 \(w_i\),所以 \(f'\) 在原图上的那部分(即去掉 \(i\) 和超源或超汇的连边)的净流量应当为 \(-w_i\)。为了让 \(f'\) 本身是流量守恒的,需连边 \(SS\to i\) 补充 \(w_i\) 流量。
换言之,\(SS\) 和 \(TT\) 的连边起到了 模拟 每个点的净流量的作用,而非补充或消耗。
我们把上述建出的图叫做 \(G'\)。我们在 \(G'\) 上跑最大流,如果所有边满流,说明可以达到流量限制,存在可行流 \(f'\),否则无解。
具体地,由斜对称性,有 \(\sum w_i =0\),所以不妨令 \(\Delta = \sum_{w_i >0} w_i =\sum_{w_i <0} -w_i\)。我们只需判断 \(\Delta\) 和跑出的最大流大小是否相等即可。
\(f' + l_i < c + l_i = r_i\),所以总的可行流 \(f\) 满足容量限制。
2. 有源汇上下界可行流
连边 \(T \rightarrow S\),容量为 \(\text{INF}\),这样形成了一个循环,转化成无源汇上下界可行流。
3. 有源汇上下界最大流
来自 Alex_Wei 好文的结论说明:
有源汇上下界最大流基于一个非常关键的结论:给定任意一组可行流,对其运行 EK 或 Dinic 等基于增广操作的最大流算法,我们总能得到正确的最大流。因为增广本身支持退流,所以无论初始的流函数 \(f\) 如何,只要 \(f\) 合法,就一定能求出最大流。
基于这个思想,我们先跑一遍有源汇上下界可行流。再撤去 \(T \rightarrow S\) 的容量为 \(\text{INF}\),然后跑一遍 \(S \rightarrow T\) 的最大流。两者相加即为答案。
这是因为 \(T \rightarrow S\) 的容量为 \(\text{INF}\) 的边是为了转化成无源汇上下界可行流而加的边。同时从 \(SS\) 连出的边与连入 \(TT\) 的边是为了跑 \(G'\) 的可行流 \(f'\) 而加的边,当然,因为我们跑的是 \(S \rightarrow T\) 的最大流,不经过 \(SS,TT\),所以没有关系。
4. 有源汇上下界最小流
还是同样的思路,找到一组有源汇上下界可行流 \(f'\)。
接着根据斜对称性,需要找到 \(f_{\min}(S,T) = -f_{\max}(T,S)\)。
所以我们求出 \(T \rightarrow S\) 的最大流,记为 \(f''\)。答案即为 \(f' - f''\)。
有负圈的费用流
在 OI 中,常用的用来解决有负圈的费用流的算法是上下界网络流。
考虑将费用为负数的边满流,这时正向边残余流量为 \(0\),所以不会出现在残量网络当中。(反向边费用为正,不必考虑)。注意这里还是要建出反边用来进行退流操作。
这个操作还是很巧妙的。满流一条边并不影响后续的操作,因为可以反悔。所以网络流本质是反悔贪心,但还涉及到图论建模,跑最大流等算法自动在退流时进行反悔。从而将原问题转成图论问题。
我们先将满流的费用算进答案中。删去虚拟源点 \(SS,TT\) 和 \(T \rightarrow S\) 的容量为 \(\text{INF}\) 的边。在新图 \(G'\) 上跑最小费用最大流即可。
trick:点权转边权
考虑将一个点拆成两个点 \(u_{in}\) 和 \(u_{out}\),边容量与点容量保持一致。
题目训练
P1343 地震逃生
第一问就是最大流(记为 \(f\)),第二问自然就是 \(\lceil \frac{x}{f} \rceil\)。
P2057 [SHOI2007] 善意的投票 / [JLOI2010] 冠军调查
图论建模好题。首先看到最小化,接着发现本质是集合划分模型。考虑最小割。
令 \(A\) 集合表示要睡觉,\(B\) 集合表示不睡觉(在 \(G\) 中的对应是源点与汇点)。
- 那么如果一个小朋友本意是要睡觉,那么从源点向这个点连一条容量为 \(1\) 的边,表示如果这条边是割边,那么违背本意,对答案贡献为 \(1\)。
- 如果本意是睡觉,那么从这个点到汇点连一条边,容量为 \(1\),表示如果作为割边对答案贡献 \(1\)。
- 好朋友之间也连一条容量为 \(1\) 的边(注意是双向边),表示如果朋友之间选择集合不同(一个是包含 \(S\) 的联通块,另一个是包含 \(T\) 的联通块),对答案产生 \(1\) 的贡献。
- 直接在 \(G\) 上跑最小割即可。
P1402 酒店之王
对于一个人来说,ta 应该有两个属性,启发我们一种建图方式,即将代表人的点放中间,代表两个属性的点放于两侧,这样可以建立起两个属性之间的图上关系。
直接连边跑最大流是错的,应该一个点可能有多个入边、多个出边。每个点 \(x\) 拆成 \(x_{in}\) 和 \(x_{out}\),边权为 \(1\) 即可解决这个问题。其实就是上面的经典 trick。
P4016 负载平衡问题
- 网络流与线性规划 24 题之一。
这题有数学做法。但我们需要用网络流来做这个题。
记平均数为 \(avg\)。那么 \(a_i > avg\) 的点是可以提供给别人货物的,反之,若 \(a_i < avg\),则它需要流入货物。
套路地建立虚拟源汇点 \(S,T\)。
- 考虑从 \(S\) 出发,向 \(a_i > avg\) 的点连容量为 \(a_i - avg\),费用为 \(0\) 的边。
- 从 \(a_i < avg\) 的点出发,连一条容量为 \(avg-a_i\),费用为 \(0\) 的点。
- 对于每个点 \(x\),找出它在环上的前驱与后继 \(pre_x,suf_x\)。连边 \(x \rightarrow pre_x\),容量为 \(INF\),费用为 \(1\)。连边 \(x \rightarrow suf_x\),容量为 \(INF\),费用为 \(1\)。
跑最小费用最大流即可。
P2053 [SCOI2007] 修车
对于每个工人而言,修第 \(k\) 辆车和修第 \(k-1\) 辆车并不等价(状态不足),所以想到拆点。类似分层图,记 \((i,j)\) 表示第 \(i\) 个工人修第 \(j\) 辆车的状态。
这里我们要进行一个分析。设一个工人修了 \(k\) 个人的车,时间分别为 \(Time_1,Time_2,\cdots Time_k\)。则总等待时间为 \(\sum\limits_{i=1}^k Time_i (k-i+1)\)。
- 但是这样建图没有正确性(因为你不知道 \(k\) 具体是多少),考虑修改状态。
- 令 \((i,j)\) 表示第 \(i\) 个工人修倒数第 \(j\) 辆车的状态。
- 那么此时对一辆车来说,它对答案的贡献就是 \(Time_i \times i\),与 \(k\) 无关,这样就能做了!
建边代码:
rep(i,1,n){
//car[i]
rep(j,1,m){
rep(k,1,n){
int x=make_id(j,k)+n;
add(i,x,1,k*cost[j][i]);
add(x,i,0,-k*cost[j][i]);
}
}
}
rep(i,1,n){
add(S,i,1,0);
add(i,S,0,0);
}
rep(i,1,m*n){
add(i+n,T,1,0);
add(T,i+n,0,0);
}
然后在建出的图 \(G\) 上跑最小费用最大流即可。
- 一个简单的正确性证明:回顾最小费用最大流的算法过程。每次需要找一个费用最小的流 \(f\),进行增广。因此我们会优先选择 \((i,j)\) 而不是 \((i,j+1)\),因为修的车越少,总等待时间就越少,故而会取一段连续的状态,保证了题目中的“同时性”。
P2770 航空路线问题
-
网络流与线性规划 24 题之一。
-
由于每个点(除起点、终点外)都只能经过一次,所以根据 trick 直接拆点,连边 \(i \rightarrow i+n\),容量为 \(1\),费用为 \(1\),其中 \(2 \le i \le n-1\),\(1\) 和 \(n\) 需要连一条容量为 \(2\) 的边到虚拟点,费用为 \(1\)。
-
同时新建虚拟源汇点 \(S,T\),连边 \(S \rightarrow 1\),\(2 \times n \rightarrow T\)。
-
对于中间的边 \((u,v)\),连边 \(u_{\text{out}} \rightarrow v_{\text{in}}\),容量 \(1\),费用为 \(0\)。
跑最大费用最大流。若最大流量是 \(2\),则有解。
注意特判 \(S \rightarrow T \rightarrow S\) 的路径情况,这时因为不经过中间点,所以流量为 \(1\)。
方案直接在残量网络上跑两次 dfs 即可。
P4843 清理雪道
新建源汇点 \(S,T\)。
建模如下:
-
对于每个点,它可以从起点直接做直升机到达,所以连边 \(S \rightarrow i\),上下界 \([0,INF]\)。
-
同时可以在中途退出,连边 \(i \rightarrow T\),上下界 \([0,INF]\)。
-
对于途中的边,它们至少需要经过一次,所以连边 \(u \rightarrow v\),上下界 \([1,INF]\)。
在建出的图 \(G\) 上跑有源汇上下界最小流即可。
P4043 [AHOI2014/JSOI2014] 支线剧情
与 P4843 思路相同。建边也一样,注意 \(S\) 为 \(1\)。这题还多了一个费用。最后跑的是最小费用可行流(注意,这里不能是最小费用最大流,因为最小费用最大流是保障在先求出最大流的同时最小化费用,我们只希望求出可行流并使费用最小)。
最终跑一遍最小费用上下界可行流。
P3980 [NOI2008] 志愿者招募
在初步接触了一些题之后,我发现很多题都是类似二分图的模式建图的,也就是集合划分。我就在想,有没有一种建图模式是直接按照序列建的?有的,这题就是。这种思路还在 P1251 出现过。
套路地建出虚拟源汇 \(S,T\)。
接下来是本题的最妙的地方。
- 对于一个点 \(i(1 \le i \le n)\),连边 \(i \rightarrow i+1\),容量为 \(INF - a_i\),费用为 \(0\)。我们把这种边记为免费边。
- 对于一组关系 \((s,t,c)\),连边 \(s \rightarrow t+1\),容量为 \(INF\),费用为 \(c\)。我们把这种边记为志愿者边。
- 最后连边 \(S \rightarrow 1\),容量为 \(INF\),费用为 \(0\)。连边 \(n \rightarrow T\),容量为 \(INF\),费用为 \(0\)。
建图的意义:免费边的上界是 \(INF - a_i\) 表示除了 \(a_i\) 流量需要强制带权的情况,剩下都可以走免费边。
\(S\) 不能设为 \(1\) 以及 \(T\) 不能设为 \(n\)。原因:\(S\) 为 \(1\) 时,流出的流量一定会大于 \(INF\),就炸了,同理如果 \(T=n\),流入的流量一定大于 \(INF\),也炸了。
- 由于题目保证有解,所以建出的图 \(G\) 的最大流一定为 \(INF\),跑最小费用最大流即可。
本题小总结:对于阶段划分的题,可以想成是序列来处理。带权边与免费边的处理不禁令人拍案叫绝。
P1674 [USACO05FEB] Secret Milking Machine G
- 最大值最小,考虑是否能二分答案。如果我们只用权值不大于 \(mid\) 可行,那么大于 \(mid\) 一定可以。存在临界情况,可以二分答案。
二分答案 \(mid\),现在需要判定是否能走 \(T\) 次路径且不经过重复边。发现这很像找增广路的 Dinic 榨汁机算法。我们建立流网络,每条边容量限制设为 \(1\)。跑最大流,记最大流为 \(f\),若 \(f \ge T\),则可行,否则不行。
参考资料
- OI-Wiki。
- llr 老师的 PPT。
- Acwing 的题解与进阶课。
- Alex_Wei 好文。

浙公网安备 33010602011771号