网络流

网络流

模型框架

现实模型:自然水供水系统。它相当于水管调度员。

  • 问题的本质是解决有向图中“资源分配与路径优化”的问题。

定义

两个特殊节点:源点(\(S\))、汇点(\(T\))。

边的关键属性:容量 \(c\)

  • 每条有向边都有一个非负实数“容量”。

性质

  • “流”作为资源的实际分配,必须满足两个核心规则,否则不是合法的网络流。
  1. 容量约束:任意一条边上的实际流量 (\(f\)),不能超过该边的容量(\(c\)),即 \(f \le c\)。例如,一根容量为 10 t 的水管,实际输水不能超过 10 t。
  2. 流量守恒:除了源点和汇点,其他所有中转节点的“流入流量总和”必须等于“输出流量总和”。
  3. 反对称性约束(斜对称):对任意 \(u,v \in V\)\(f(u,v) = -f(v,u)\)
  4. 弧流量约束(容量限制):\(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 框架上做两个优化:

  1. 分层(BFS):先给节点按“到源点的最短距离”分层,保证增广路是“最短路径”;
  2. 多路增广(DFS):在分层图中用 DFS 批量找增广路,且用“当前弧优化”避免重复遍历有效边。
  • 大体流程为:
  1. 用 bfs 求出 G 中每个点的深度。
  2. 用 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 的一张图:

最大流最小割定理

最大流最小割定理:网络的最大流量等于割的最小容量。

最大流最小割定理是贯穿网络流始终的核心。它是网络流相关理论的最基本结论。

  1. 任意可行流的流量不大于任意割的容量。

    证明:考虑任何可行流和任意割 \((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) \]

  2. 对于整数容量网络,存在一个可行流的流量等于一个割的容量。

    证明:考虑进行有限次增广后残量网络的结果。此时一定有 \(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)\),要求:

  1. \(0 \leq f(u,v) \leq w(u,v)\)(每条边的流量不超过其流量限制);
  2. \(\forall p \in \{V \setminus \{s,t\}\}\)\(\sum_{(i,p) \in E}f(i,p)=\sum_{(p,i)\in E}f(p,i)\)(除了源点和汇点外,其他各点流入的流量和流出的流量相等);
  3. \(\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

证明思想:考虑证明:

  1. 对于 \(G'\) 中的每一条可行流,都对应着 \(G'\) 中的一条可行流。
  2. 对于 \(G\) 中的每一条可行流,都对应着 \(G'\) 中的一条可行流。
  3. 于是得到 \(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\) 区分)。

分类讨论:

  1. \(d(u)>0\),那么从 \(u\) 流出的流量大于流入的流量,需要补充入流量,将 \(SS\) 连一条容量为 \(d(u)\)\(u\)
  2. \(d(u)<0\),入流量大于出流量,那么需要补充出流量,故连边 \(u \rightarrow TT\),容量为 \(|d(u)|=-d(u)\)
  3. \(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\)

  1. 考虑从 \(S\) 出发,向 \(a_i > avg\) 的点连容量为 \(a_i - avg\),费用为 \(0\) 的边。
  2. \(a_i < avg\) 的点出发,连一条容量为 \(avg-a_i\),费用为 \(0\) 的点。
  3. 对于每个点 \(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\),则可行,否则不行。

record

参考资料

  1. OI-Wiki。
  2. llr 老师的 PPT。
  3. Acwing 的题解与进阶课。
  4. Alex_Wei 好文
posted @ 2025-12-10 19:31  lbh666  阅读(52)  评论(0)    收藏  举报