目录
一些概念
网络 是指一张特殊的 有向图 G = ( V , E ) G = (V,E) G=(V,E) ,在点集中存在两个特殊的点 s s s ( 源点 ) 和 t t t ( 汇点 ) ,每条边有边权 c ( u , v ) c(u,v) c(u,v) 表示这条边的 容量 ( 特别的,若边集中没有 ( u , v ) (u,v) (u,v) 这条边,则 c ( u , v ) = 0 c(u,v)=0 c(u,v)=0 )
同时,每条边 具有流量 f ( u , v ) f(u,v) f(u,v) ( 通常是非负整数 )
定义 一个网络的 可行流 f f f 为 关于边集 E E E 的函数 f ( E ) f(E) f(E) ,且满足以下性质:
- 容量限制:对于每条边,有 0 ≤ f ( u , v ) ≤ c ( u , v ) 0\leq f(u,v) \leq c(u,v) 0≤f(u,v)≤c(u,v) ;
- 流量守恒:除源汇点外,每个节点总流入等于总流出,即 ∑ ( u , x ) ∈ E f ( u , x ) = ∑ ( u , x ) ∈ E f ( x , v ) \sum_{(u,x)\in E}f(u,x)=\sum_{(u,x)\in E} f(x,v) ∑(u,x)∈Ef(u,x)=∑(u,x)∈Ef(x,v);同时我们定义点 u u u 的净流量 f ( u ) = ∑ f ( u , x ) − ∑ f ( x , v ) f(u) = \sum f(u,x)-\sum f(x,v) f(u)=∑f(u,x)−∑f(x,v)
定义 f f f 的流量 ∣ f ∣ = f ( s ) = − f ( t ) |f|=f(s)=-f(t) ∣f∣=f(s)=−f(t)
对于一张网络 G = ( V , E ) G=(V,E) G=(V,E) , 如果两个点集 [ S , T ] [S,T] [S,T] 是 V V V 的划分(即 S ∪ T = V S\cup T=V S∪T=V 且 S ∩ T = ∅ S\cap T=\emptyset S∩T=∅ ),同时 s ∈ S s\in S s∈S 且 t ∈ T t\in T t∈T ,那么称 [ S , T ] [S,T] [S,T] 是该网络的一个割。定义割的容量 ∣ S , T ∣ = ∑ u ∈ S ∑ v ∈ T c ( u , v ) |{S,T}|=\sum_{u\in S}\sum_{v\in T}c(u,v) ∣S,T∣=∑u∈S∑v∈Tc(u,v)
注意区别于 割的流量 —— f ( [ S , T ] ) = ∑ u ∈ S ∑ v ∈ T f ( u , v ) − ∑ u ∈ T ∑ v ∈ S f ( u , v ) f([S,T])=\sum_{u\in S}\sum_{v\in T}f(u,v)-\sum_{u\in T}\sum_{v\in S}f(u,v) f([S,T])=∑u∈S∑v∈Tf(u,v)−∑u∈T∑v∈Sf(u,v)
网络流通常包括以下几种问题 :
- 最大流问题 :在一张网络中求使 ∣ f ∣ |f| ∣f∣ 最大的可行流
- 最小割问题:求 容量 最小的割
- 费用流问题
最大流
为求解最大流问题,我们先定义以下概念:
- 对于一条边 ( u , v ) (u,v) (u,v),定义其 剩余容量边 容量为 c ′ ( v , u ) = c ( u , v ) − f ( u , v ) c' (v,u)=c(u,v)-f(u,v) c′(v,u)=c(u,v)−f(u,v) ,方向从 v -> u (可以简称为 ”反向边“ )
- 将 G G G 中 点集 V V V、原边集 E E E 和 剩余容量大于 0 0 0 的边 称为 G G G 的残留网络 G f G_f Gf
- 在残留网络上 一条从 s s s 到 t t t 的路径称为增广路
对于每条现存的增广路,我们都可以沿增广路从源点 s s s 向 t t t 增大流量,因此最大流的求解就可以看作找增广路增广的过程。
考虑下面一个结论:
f f f 是 G G G 的最大流 当且仅当 残留网络 G f G_f Gf 上不存在增广路
该结论进一步推广后就是
最大流—最小割定理
该定理指出,在一张网络中,1.可行流 f f f 是最大流 、2.残留网络 G f G_f Gf 上没有增广路、3.存在某个割 { S , T S,T S,T} 使得 ∣ S , T ∣ = ∣ f ∣ |S,T|=|f| ∣S,T∣=∣f∣ 三者完全等价
Pf :
从 1 -> 2 是显然的,若 G f G_f Gf 上存在一条增广路,我们就可以沿增广路从 s s s 到 t t t 增大流量
对于从 2 -> 3 :
首先我们需要一个引理:
对于网络 G G G 的 任意一个 可行流 f f f 和 任意一个 割 { S , T S,T S,T} ,总有 ∣ f ∣ ≤ ∣ S , T ∣ |f|\leq |S,T| ∣f∣≤∣S,T∣ ,其中等号成立当且仅当 { ( u , v ) ∣ u ∈ S , v ∈ T } \left \{ (u,v)|u\in S ,v\in T \right \} {(u,v)∣u∈S,v∈T} 均为满流 且 { ( v , u ) ∣ u ∈ S , v ∈ T } \left \{ (v,u)|u\in S ,v\in T \right \} {(v,u)∣u∈S,v∈T} 均为空流
那么
G
G
G 的任何一个 割 {
S
,
T
S,T
S,T} 都满足
∣
S
,
T
∣
≥
∣
f
m
a
x
∣
|S,T|\geq|f_{max}|
∣S,T∣≥∣fmax∣ ,
f
m
a
x
f_{max}
fmax 是最大流
下面证明对于任何一个网络,这里的等号都能取到:
对于一个没有增广路的残留网络 G f G_f Gf ,我们尝试进行构造
首先,从 s s s 出发,沿剩余容量 严格大于零的边 尽可能走,记能到达的点集 为 S S S,易知 t ∉ S t\notin S t∈/S
记 T T T = V / S V/S V/S , 那么 { S , T S,T S,T} 是原网络的一个割
那么,对于任意 u ∈ S , v ∈ T u\in S,v\in T u∈S,v∈T 均有 c f ( u , v ) = 0 c_f(u,v)=0 cf(u,v)=0
讨论一下:
- 若 ( u , v ) ∈ E (u,v)\in E (u,v)∈E ,则 c f ( u , v ) = c ( u , v ) − f ( u , v ) = 0 c_f(u,v)=c(u,v)-f(u,v)=0 cf(u,v)=c(u,v)−f(u,v)=0 , 则 f ( u , v ) = c ( u , v ) f(u,v)=c(u,v) f(u,v)=c(u,v) ,满流
- 若 ( v , u ) ∈ E (v,u)\in E (v,u)∈E ,则 c f ( u , v ) = c ( u , v ) − c f ( v , u ) = f ( u , v ) = 0 c_f(u,v)=c(u,v)-c_f(v,u)=f(u,v)=0 cf(u,v)=c(u,v)−cf(v,u)=f(u,v)=0 , 空流
两个条件均满足,则等号成立
那么 ∣ S , T ∣ = ∣ f ∣ |S,T|=|f| ∣S,T∣=∣f∣ ,于是我们成功由 2 推导到 3
接下来 3 -> 1 :
我们已知了 ∣ S , T ∣ ≥ ∣ f m a x ∣ |S,T|\geq|f_{max}| ∣S,T∣≥∣fmax∣ ,结合刚刚得出的 ∣ S , T ∣ = ∣ f ∣ ≤ ∣ f m a x ∣ |S,T|=|f|\leq |f_{max}| ∣S,T∣=∣f∣≤∣fmax∣ , 不难得到 ∣ S , T ∣ = f m a x ∣ |S,T|=f_{max}| ∣S,T∣=fmax∣
于是该定理得证
算法实现 —— FF 增广
基于上面的讨论,我们求解最大流的方法就是在残留网络上不断寻找增广路后进行增广
这本质上是一种贪心的做法,由于反向边的存在,我们在进行一次 非全局最优 的操作后可以顺着反向边 “退流” ,保障了正确性
EK 算法
#include<bits/stdc++.h>
using namespace std ;
typedef long long LL ;
const int N = 210 , M = 5010 , INF = INT_MAX ;
int n , m , s , T ;
struct nn
{
int lst , to , val ;
}E[2*M] ;
int tot = 1 , head[N] ;
inline void add ( int x , int y , int z )
{
E[++tot] = (nn){ head[x] , y , z } ;
head[x] = tot ;
E[++tot] = (nn){ head[y] , x , 0 } ;
head[y] = tot ;
}
queue<int> q ;
int dis[N] , pre[N] ;
bool bfs () // 找增广路
{
while( !q.empty() ) q.pop() ;
memset( pre , 0 , sizeof pre ) ;
pre[s] = 1 ; dis[s] = INF ;
q.push( s ) ;
while( !q.empty() ) {
int x = q.front() ; q.pop() ;
for(int i = head[x] ; i ; i = E[i].lst ) {
int t = E[i].to ;
if( !pre[t] && E[i].val ) {
pre[t] = i ; dis[t] = min( E[i].val , dis[x] ) ;
if( t == T ) return 1 ;
q.push( t ) ;
}
}
}
return 0 ;
}
int main()
{
scanf("%d%d%d%d" , &n , &m , &s , &T ) ;
int x , y , z ;
for(int i = 1 ; i <= m ; i ++ ) {
scanf("%d%d%d" , &x , &y , &z ) ;
add( x , y , z ) ;
}
LL ans = 0 ;
while( bfs() ) {
// 更新残留网络
ans += dis[T] ;
for(int now = T ; now != s ; now = E[pre[now]^1].to ) {
E[pre[now]].val -= dis[T] , E[pre[now]^1].val += dis[T] ;
}
}
printf("%lld\n" , ans ) ;
return 0 ;
}
复杂度较高,不推荐
Dinic 算法
从 EK 的实现上我们可以看出,每次执行整张图的 BFS ,却只对其中一条路径增广,效率很低
Dinic 的解决方法是 一次遍历中增广多条路径
为了避免出现增广过程中 交叉、流量重复增加的情况,首先对 整张图进行 分层
不是很懂,大概就是多条增广路的并
这里可能会出现小问题:
这个优化是必要的
CODE
#include<bits/stdc++.h>
using namespace std ;
const int M = 5000+100 , N = 210 ;
typedef long long LL ;
const LL INF = 1e16 ;
int n , m , S , T ;
struct nn
{
int lst , to , val ;
}E[2*M] ;
int head[N] , tot = 1 ;
inline void add ( int x , int y , int val )
{
E[++tot] = (nn){ head[x] , y , val } ;
head[x] = tot ;
}
int d[N] , cur[N] ; // 分层 / 当前弧 优化
bool bfs() // 判断是否有增广路 and 分层
{
queue<int> q ;
memset( d , -1 , sizeof d ) ;
d[S] = 0 , cur[S] = head[S] ;
q.push( S ) ;
while( !q.empty() ) {
int x = q.front() ; q.pop() ;
for(int i = head[x] ; i ; i = E[i].lst ) {
int t = E[i].to ;
if( d[t] == -1 && E[i].val ) {
d[t] = d[x] + 1 ;
cur[t] = head[t] ;
q.push( t ) ;
}
}
}
if( d[T] == -1 ) return 0 ; // 没有增广路惹
return 1 ;
}
LL Find( int x , LL limit ) // 求从 x 节点往汇点还能增广多少
{
if( x == T ) {
return limit ;
}
LL flow = 0 ;
for(int i = cur[x] ; i && flow < limit ; i = E[i].lst ) { // 这个优化也是必要的
int t = E[i].to ;
cur[x] = i ;
if( d[t] == d[x]+1 && E[i].val ) {
LL nf = Find( t , min(limit-flow,1LL*E[i].val) ) ;
if( nf == 0 ) d[t] = -1 ; // 不再走
flow += nf , E[i].val -= nf , E[i^1].val += nf ;
}
}
return flow ;
}
int main()
{
scanf("%d%d%d%d" , &n , &m , &S , &T ) ;
int x , y , z ;
for(int i = 1 ; i <= m ; i ++ ) {
scanf("%d%d%d" , &x , &y , &z ) ;
add( x , y , z ) , add( y , x , 0 ) ;
}
LL ans = 0 ;
while( bfs() ) {
LL flow = 0 ;
while( flow = Find( S , INF ) ) ans += flow ;
}
printf("%lld" , ans ) ;
return 0 ;
}
唯一的细节就是两个优化 : 当前弧 和 流量达到 limit 后 return
复杂度 O(可过) ( 对于 99% 的题 是这样的)
经典模型
1.1 无源汇上下界可行流
给你一张网络,要求每条边的流量在 [ l i , r i ] [l_i,r_i] [li,ri] 之间,同时每个点满足流量守恒,问是否存在一种方案是的这些条件成立?
建图:
对于每条边,先钦定下界满足,把容量限制变成 [ 0 , r i − l i ] [0,r_i-l_i] [0,ri−li]
然后会发现这样钦定的话会使得某些点不满足流量守恒,设点 i i i 的净流量为 v i v_i vi
- 若 v i > 0 v_i>0 vi>0,建边 ( S , i , v i ) (S,i,v_i) (S,i,vi)
- 若 v i < 0 v_i<0 vi<0,建边 ( i , T , − v i ) (i,T,-v_i) (i,T,−vi)
合法的条件是这张网络存在满流,直接跑最大流即可
1.2 有源汇上下界可行流
给你一张网络,有源、汇点 S , T S,T S,T,要求每条边的流量在 [ l i , r i ] [l_i,r_i] [li,ri] 之间,同时除了源 S , T S,T S,T 之外的每个点满足流量守恒,问是否存在一种方案是的这些条件成立?
建图:
多连一条边 ( T , S , inf ) (T,S,\inf) (T,S,inf),使得 S , T S,T S,T 也满足流量守恒
然后跑无源汇的即可
1.3 有源汇上下界最大流
给你一张网络,有源、汇点 S , T S,T S,T,要求每条边的流量在 [ l i , r i ] [l_i,r_i] [li,ri] 之间,同时除了源 S , T S,T S,T 之外的每个点满足流量守恒,问从 S S S 到 T T T 的最大流是多少?
先跑出一个可行流,记录此时新加边的反边的流量 a n s 1 ans1 ans1,即为此时 S S S 到 T T T 的流量
删去新连的边 ( T , S , inf ) (T,S,\inf) (T,S,inf),再在残留网络上跑 S S S 到 T T T 的最大流,加上 a n s 1 ans1 ans1 就是答案
容易证明第二次跑最大流时 不会使得第一次跑出的可行流变的不合法
1.3 有源汇上下界最小流
前面几步一样,最后到第二次跑最大流时,跑 T T T 到 S S S 的最大流,然后让 a n s 1 ans1 ans1 减去这个即可
一些 trick
-
建立超级源点、超级汇点
-
求最大流的关键边,定义为 增大容量 就能使 最大流增大的边
关键边判定方法:跑完最大流后,从 S S S 开始沿着边权 > 0 >0 >0 的边走,标记这些点;从 T T T 开始同理。
一条边能作为关键边当且仅当 满流 且两端点分别被 S S S 和 T T T 标记
-
限制 “某个东西只能最多用 d i d_i di 次” 的技巧:拆成入点 x x x 和出点 x ′ x' x′,连边 ( x , x ′ , d i ) (x,x',d_i) (x,x′,di) example
最小割
求法
根据 最大流-最小割 定理,直接跑最大流即可
对于构造,考虑在残留网络上从源点 s s s 出发,沿剩余容量 > 0 >0 >0 的边走,标记经过的点加入集合 S S S
记 S S S 的补集为 T T T, [ S , T ] [S,T] [S,T] 是这张网络的 一个 最小割
一张网络的最小割可能 不唯一,这是由于,从 s , t s,t s,t 分别出发沿剩余容量 > 0 >0 >0 的边走,标记的点确实分别属于 S S S 和 T T T,但可能存在一些点未被标记到,这些点属于哪个集合是任意的
模型
求割边数量
对于一条边 ( x , y , v ) (x,y,v) (x,y,v),将边权修改为 v × U + 1 v\times U+1 v×U+1,其中 U U U 是极大值,再跑最小割
基本模型
有 n n n 件物品和两个集合 A , B A,B A,B,若物品 i i i 没有 放入 A A A 集合会产生 a i a_i ai 的代价,没有 放入 B B B 集合会产生 b i b_i bi 的代价;同时还有若干条限制形如 ( x i , y i , v i ) (x_i,y_i,v_i) (xi,yi,vi) 表示若 x i x_i xi 与 y i y_i yi 不在同一个集合,会产生 v i v_i vi 的代价。每个物品能且仅能属于一个集合,问最小代价?
最小割很适合来处理这种 二者选其一 的问题,我们设置源点 s s s、汇点 t t t,对于第 i i i 件物品连边 ( s , i , a i ) (s,i,a_i) (s,i,ai), ( i , t , b i ) (i,t,b_i) (i,t,bi)
对于物品之间,在 x i x_i xi 和 y i y_i yi 之间连容量为 v i v_i vi 的双向边
容易验证该网络的每个割都和原问题的划分一一对应,最小割即为最小代价
平面图最小割 转 对偶图最短路
最常见的是网格图,转成求最短路后复杂度大大降低
最大权闭合图
给你一张有向图,每个点有点权 a i a_i ai(可正可负),你需要选择一个权值和最大的子图,满足子图中任意一个点的后继节点也在子图中
建图:
设置源点 s s s、汇点 t t t,对于每件物品分类讨论:
- 若 a i > 0 a_i>0 ai>0,连边 ( s , i , a i ) (s,i,a_i) (s,i,ai)
- 若 a i < 0 a_i<0 ai<0,连边 ( i , t , − a i ) (i,t,-a_i) (i,t,−ai)
对于原图中的边 ( x , y ) (x,y) (x,y),在网络中连边 ( x , y , inf ) (x,y,\inf) (x,y,inf)
答案为 所有正点权的点权值之和 - 最小割
Tip:
最小割问题中经常会像这样连接 ( x , y , inf ) (x,y,\inf) (x,y,inf) 的边,表示 x , y x,y x,y 必须 在被划分在一个集合中
最大密度子图
1
最小点权覆盖集
给你一张图,点权均为正,要求选择一些点,使得对于每条边,至少有一个端点被选上,问选出点的最小权值和?
一般图是 N P C NPC NPC,二分图可以做
如果没有点权,根据 König定理
:二分图最小点覆盖 = 最大匹配
有点权的话考虑转化为最小割模型:
对于左部点,连边 ( s , i , a i ) (s,i,a_i) (s,i,ai);对于右部点连边 ( i , t , a i ) (i,t,a_i) (i,t,ai);对于原图中的边,连 ( x , y , inf ) (x,y,\inf) (x,y,inf)
最大点权独立集
不带权的话,有 n n n − - − 最小点覆盖集 = = = 最大点独立集
带权就是把 n n n 改成 s u m sum sum
这是由于任何一个覆盖集的补集都是独立集
最小路径覆盖
给你一张 D A G DAG DAG,要求选出若干不交路径(点不交;可以只选一个点)使得每个点都被覆盖恰好一次,问最少选出的路径条数?
结论是: D A G DAG DAG 的最小路径覆盖 = n n n - 拆点二分图的最大匹配
拆点二分图这么建:对于每条边 ( x , y ) (x,y) (x,y),建 ( x , y + n ) (x,y+n) (x,y+n)
首先 最小路径覆盖 = ∑ ( 点 − 边 ) \sum (点-边) ∑(点−边) = n n n - 在路径覆盖中的边数
希望最大化后面那个
拆点二分图上的最大匹配恰好就是这个,画画图十分形象
构造只需要沿匹配边从左部走到右部,再到这个右部点对应的左部点,重复这个过程
另一种问题是 最小路径可重覆盖,意思是路径可以交
这种情况下先传递闭包再跑上述算法即可
文理分科模型
这道题目太经典了
处理这种 “若 a , b , c , d a,b,c,d a,b,c,d” 同时 属于集合 A A A,则获得 a i a_i ai 的价值,考虑建出虚点,利用割的性质来刻画
切糕模型(距离限制模型)
感觉这道题又刷新了我对最小割的理解了!
利用割的性质来刻画!
最小割树
给一张无向图,多次询问 S i , T i S_i,T_i Si,Ti 之前的最小割?
做法是分治,每次从图中随机选两个点 u , v u,v u,v,跑最小割,连边 ( u , v , ∣ f ∣ ) (u,v,|f|) (u,v,∣f∣);然后整张图会被划分成源点、汇点集合,对两个集合分别递归这个过程
显然最后建出的是一棵树,两点间的最小割即为两点间树上路径的最小值
费用流
一般可以分为 最大费用最大流 和 最小费用最大流
每条边的信息描述为 ( x , y , v , c ) (x,y,v,c) (x,y,v,c),表示容量为 v v v,且每流经 1 1 1 的流量就要消耗 c c c 的费用
只能求解在 最大流 时的费用最值
求法
一般采用 E K EK EK,把 b f s \mathrm{bfs} bfs 找增广路换成 s p f a \mathrm{spfa} spfa 即可
#include<bits/stdc++.h>
using namespace std ;
typedef long long LL ;
const int N = 410 , M = 15100 , inf = 0x3f3f3f3f ;
int n , m , S , T ;
struct nn
{
int lst , to , val , c ;
}E[M<<1] ;
int head[N] , tot = 1 ;
inline void add( int x , int y , int v , int c )
{
E[++tot] = (nn){head[x],y,v,c} ;
head[x] = tot ;
}
queue<int> q ;
bool vis[N] ;
int f[N] , d[N] , pre[N] ;
bool spfa()
{
memset( d , 0x3f , sizeof d ) ;
f[S] = inf , d[S] = 0 ;
q.push(S) ; vis[S] = 1 ;
while( !q.empty() ) {
int x = q.front() ; q.pop() ;
vis[x] = 0 ;
for(int i = head[x] ; i ; i = E[i].lst ) {
int t = E[i].to ;
if( E[i].val && d[t]>d[x]+E[i].c ) {
d[t] = d[x]+E[i].c ;
f[t] = min(f[x],E[i].val) ;
pre[t] = i ;
if( !vis[t] ) q.push(t) , vis[t] = 1 ;
}
}
}
if( d[T] != inf ) return 1 ;
return 0 ;
}
int main()
{
scanf("%d%d" , &n , &m ) ;
S = 1 , T = n ;
int x , y , c , w ;
for(int i = 1 ; i <= m ; i ++ ) {
scanf("%d%d%d%d" , &x , &y , &c , &w ) ;
add(x,y,c,w) ; add(y,x,0,-w) ;
}
int ans1 = 0 , ans2 = 0 ;
while( spfa() ) {
ans1 += f[T] ; ans2 += f[T]*d[T] ;
for(int u = T ; u != S ; u = E[pre[u]^1].to ) {
E[pre[u]].val -= f[T] , E[pre[u]^1].val += f[T] ;
}
}
printf("%d %d\n" , ans1 , ans2 ) ;
return 0 ;
}
建模技巧
费用流的问题类型很多,基本没什么固定的模型,需要具体题目具体分析,关键是找到 “流” 和 “费用” 分别对应题目中的什么量
但还是存在一些 奇技淫巧 的
拆点
处理形如 “经过多次只算一次贡献” 的限制时,考虑把每个点拆成入点 x x x,出点 x ′ x' x′,连边 ( x , x ′ , 1 , c ) , ( x , x ′ , inf , 0 ) (x,x',1,c),(x,x',\inf,0) (x,x′,1,c),(x,x′,inf,0)
有源汇上下界最小费用可行流
挺牛逼的,按跑上下界可行流的方式直接费用流就行
“一面对多面”
看网上别人的博客都起这个名字,我也这么叫吧
主要是从 P3980 [NOI2008] 志愿者招募 这道题中出现的
我的理解是这样:
流只能实现一对一,不可能一个流覆盖多个点
考虑从反面做,转化成 “流不覆盖区间若干次”,这种情况下只需要流直接跨过区间即可
那么对于限制就要连负权边,最后每条边都加上极大值即可
听到的另一种做法:
无源汇上下界最小费用可行流
对于每个 i i i 建一条 i i i 指向 i + 1 i+1 i+1,容量下界为 a i a_i ai 的边
对于每个志愿者,我们希望他 “在 s i s_i si 进入,在 t i t_i ti 流出”,那么就从 t i t_i ti 向 s i s_i si 连边
P7425 [THUPC2017] 机场
直接放 例题 了
费用流是没法同时维护两个时间轴的
这种时候一般考虑分析性质,只进行最优化的操作
这道题还有一点是关于决策的位置,不能丢失 “这个流是代表的是哪个飞机” 这个信息
那么这样连边:对每个飞机建一个点,由不同的决策位置连向该点,费用为决策的代价,最后该点向汇点连容量为 1 1 1 的边
有负圈的费用流
可以转化为上下界费用最大流来做
对于一条边 ( x , y , v , c ) (x,y,v,c) (x,y,v,c),若 c < 0 c<0 c<0,考虑把它拆成两条边:
- ( x , y , [ v , v ] , c ) (x,y,[v,v],c) (x,y,[v,v],c),意思是强制这条边满流,不需要把这条边加入网络,直接算贡献
- ( y , x , [ 0 , v ] , − c ) (y,x,[0,v],-c) (y,x,[0,v],−c),意思是给予它退流的机会
容易发现这样与原问题是等价的,然后跑上下界费用最大流即可
具体的:
建立虚拟源汇
s
s
,
t
t
ss,tt
ss,tt;
对每个点进行流量的补齐,同时加入
(
T
,
S
,
inf
,
0
)
(T,S,\inf,0)
(T,S,inf,0);
跑
s
s
ss
ss 到
t
t
tt
tt 费用流;
删去
(
T
,
S
inf
,
0
)
(T,S\inf,0)
(T,Sinf,0);
跑
S
S
S 到
T
T
T 的费用流