网络流总结
最大流模型
Dinic 最大流板子
注意三个优化:
· flow < lim 当前流不能大于上一层限制
· now[u]=i 当前弧优化
· if(flow==0) dep[u]=0; 我与终点不连通 上一层不要信任我
无源汇上下界可行流
-
建图:
对于新图的每个边 f ′ ( u , v ) = c u ( u , v ) − c l ( u , v ) f'(u,v)=c_u(u,v)-c_l(u,v) f′(u,v)=cu(u,v)−cl(u,v)
每个点 A [ i ] = C i n − C o u t A[\ i\ ]= C_{in} -C_{out} A[ i ]=Cin−Cout
对于每个 A [ i ] < 0 A[\ i\ ]<0 A[ i ]<0 的点u 建一条v–>T
对于每个 A [ i ] > 0 A[\ i\ ]>0 A[ i ]>0 的点v 建一条S–>u
add(u,v,c,d); A[u]-=c; A[v]+=c; //c是下界
-
容量限制: c l ( u , v ) < = f ( u , c ) < = c u ( u , v ) c_l(u,v)<=f(u,c)<=c_u(u,v) cl(u,v)<=f(u,c)<=cu(u,v)
流量守恒:
在新图中 c ( s , u ) = δ = c i n − c o u t c(s,u)= \delta=c_{in}-c_{out} c(s,u)=δ=cin−cout
对于新图流量守恒 有 δ + y = x \delta+y=x δ+y=x
即 y + c i n − c o u t = x y+c_{in}-c_{out}=x y+cin−cout=x
移项得 y + c i n = x + c o u t y+c_{in}=x+c_{out} y+cin=x+cout
则原图流量守恒得证 -
只有当新图中与 S S S相连的所有边满流,则对应的,即使原图中 C 入 − C 出 = 0 C_入 -C_出 =0 C入−C出=0 ,才能使原图遵循流量守恒, 因此 —— G f G_f Gf 的最大流为 G G G 的可行流
-
代码细节:
1.记录与S相连的边的容量和,判断新图是否有最大流if(dinic()!=tot) printf("NO");
2.求原图每条边流量时,一定要加下界(边的流量为其反向边的容量)e[i^1].w + l[i]
for(int i=1;i<=m;i++){
int u=read(), v=read(), c=read(), d=read();
add(u,v,c,d);
A[u]-=c; A[v]+=c;
}
int tot=0;
for(int i=1;i<=n;i++){
if(A[i]>0) add(s,i,0,A[i]), tot+=A[i];
else if(A[i]<0) add(i,t,0,-A[i]);
}
if(dinic()!=tot) printf("NO"); //若新图没有最大流 则原图不存在可行流
else {
printf("YES\n");
for(int i=2;i<=m*2;i+=2)
printf("%d\n",e[i^1].w+l[i]);
}
有源汇上下界最大流
- 建图:
step1: 设虚拟源汇点ST,转化为无源汇问题,所有边权 f ′ ( u , v ) = c u ( u , v ) − c l ( u , v ) f'(u,v)=c_u(u,v)-c_l(u,v) f′(u,v)=cu(u,v)−cl(u,v);
step2: 加边 c ( t , s ) = i n f c(t,s)=inf c(t,s)=inf
step3: 跑一遍S->T最大流,取s->t一条可行流 f s − > t f_{s->t} fs−>t
step4: 删去 c ( s , t ) c(s,t) c(s,t)
step5: 起点S=s; 终点T=t 在残留网络中跑一遍最大流,得到s->t在残网中的最大流 f 0 ′ f_0' f0′
则原图的最大流为 f 0 = f s − > + f 0 ′ f_0=f_{s->}+f_0' f0=fs−>+f0′
结论: f 0 = f s − > t + f 0 ′ f_0=f_{s->t}+f_0' f0=fs−>t+f0′ - 思考
为了简化问题,先将有源汇问题转化为无源汇问题,增加两个虚点ST
step2中从t->s建立一条容量为inf的边,是为了保证原图源汇点st的属性,同时满足新图中st点的流量守恒
可行流 f s − > t f_{s->t} fs−>t并不是最大流,因此还要在原图的残留网络中(删去新增边),跑一遍最大流,“榨干”网络
for(int i=1;i<=m;i++){
int u=read(), v=read(),c=read(), d=read();
add(u,v,c,d);
A[u]-=c, A[v]+=c;
}
int tot=0;
for(int i=1;i<=n;i++){
if(A[i]>0) add(S,i,0,A[i]), tot+=A[i];
else if(A[i]<0) add(i,T,0,-A[i]);
}
add(t,s,0,inf);
if(tot!=dinic()) printf("No Solution");
else{
int res=e[cnt].w;
e[cnt].w=e[cnt-1].w=0;
S=s; T=t;
res+=dinic();
printf("%d",res);
}
有源汇上下界最小流
- 建图:
与最大流的区别:交换 起点S=t; 终点T=s,即跑一遍t->s最大流,(相当于在s->t的可行流的基础上,极大限度地退回t->s的流),即 f s − > t − f 0 ′ f_{s->t}-f_0' fs−>t−f0′
S=t; T=s;
res-=dinic();
printf("%d",res);
多源汇最大流
- 建图:
建立虚拟源汇点ST,为每个源点建一条S到其的边;
对于每一个汇点建一条其到T的边,容量为正无穷
最大流之关键边
关键边:增加该边的容量,就会在残留网络中产生增广路,使原最大流增大。
求关键边方案:
- 跑一遍dinic 榨干网络
- 寻找所有满流边,关键边一定在所有的满流边之中(但满流边不一定是关键边)
- 只有当该边的入点与残留网络中的S联通 且 该边的出点与残留网络中的T联通,该边为关键边
void dfs(int u,bool st[],int ty){ //寻找关键边核心代码
st[u]=1;
for(int i=head[u];i;i=e[i].next ){
int v=e[i].to ;
int j=i^ty;
if(e[j].w&&!st[v]){
dfs(v,st,ty);
}
}
}
int res=0;
for(int i=2;i<=cnt;i+=2)
if(!e[i].w&&v1[e[i^1].to]&&v2[e[i].to])
res++;
拆点
若对于一个点有限制,可将点拆为入点和出点,将对于点的限制加到入点和出点的连边上。
注意!!!!!!!!!!!!!!!!!!!!!!!!!
无向图拆点之后变成有向图,反向边是0
例题(血的教训):P1345 [USACO5.4]奶牛的电信Telecowmunication
最小割模型
最大权闭合子图
闭合图:点集中的点没有指向点集外的边
最大权闭合子图就是原图G中一个点权最大的闭合子图
一般在点权存在负权的图中讨论,(点权为正的话那最大权闭合图就是整个图咯)
- 建图:
{ c ( u , v ) = ∞ c ( s , v ) = W v c ( v , t ) = ∣ W v ∣ \left\{ \begin{matrix} c(u,v)=\infty \\ c(s,v)=W_v \\ c(v,t)=|W_v| \end{matrix} \right. ⎩ ⎨ ⎧c(u,v)=∞c(s,v)=Wvc(v,t)=∣Wv∣
对于原图所有边,容量为正无穷
对于所有正权点,从S连一条容量为其点权的边
对于所有负权点,向T连一条容量为其点券的绝对值的边
最终最大闭合子图的点权和为
W
(
v
1
)
=
W
(
v
+
)
−
C
[
S
,
T
]
W(v_1)=W(v^+)-C[S,T]
W(v1)=W(v+)−C[S,T]
证明过程就不写了(markdown麻烦)
- 求方案
从源点S开始搜索,在残留网络中沿着容量大于零的边走,所有遍历到的构成集合S,剩余的构成集合T。集合S即为所求的最大权闭合子图所包含的点。
最大密度子图
- 图的密度: g = ∣ E ′ ∣ ∣ V ′ ∣ g=\frac{|E'|}{|V'|} g=∣V′∣∣E′∣
M a x i m i z e h ( g ) = ∣ E ′ ∣ − g ∣ V ′ ∣ Maximize \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ h(g)=|E'|-g|V'| Maximize h(g)=∣E′∣−g∣V′∣
一般使用二分求解
转化一下
M
i
n
i
m
i
z
e
h
(
g
)
=
g
∣
V
′
∣
−
∣
E
′
∣
Minimize \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ h(g)=g|V'|-|E'|
Minimize h(g)=g∣V′∣−∣E′∣
h ( g ) = g ∣ V ′ ∣ − ∣ E ′ ∣ = ∑ g − ∑ 1 = ∑ g − ( ∑ d v 2 + c [ v ′ , v ‾ ′ ] 2 ) = 1 2 ( ∑ 2 g − ∑ d v + c [ v ′ , v ‾ ′ ] ) = 1 2 ( ∑ ( 2 g − d v ) + c [ v ′ , v ‾ ′ ] ) \begin{aligned} h(g) &=g|V'|-|E'| \\ &=\sum g- \sum 1\\ &=\sum g-(\frac{\sum dv}{2}+\frac{c[v',\overline v']}{2} ) \\ &=\frac{1}{2}(\sum 2g-\sum dv+c[v',\overline v']) \\ &=\frac{1}{2}(\sum ( 2g-dv)+c[v',\overline v']) \end{aligned} h(g)=g∣V′∣−∣E′∣=∑g−∑1=∑g−(2∑dv+2c[v′,v′])=21(∑2g−∑dv+c[v′,v′])=21(∑(2g−dv)+c[v′,v′])
- 建图:
可考虑以 2 g − d v 2g-dv 2g−dv 作为边权 由于该值可能为负,因此考虑给所有边加上一个极大值U
{ c ( u , v ) = 1 c ( s , v ) = U c ( v , t ) = U + 2 g − d v U = m \left\{ \begin{matrix} c(u,v)=1 \\ c(s,v)=U \\ c(v,t)=U+2g-d_v \\ \end{matrix} \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ U=m \right. ⎩ ⎨ ⎧c(u,v)=1c(s,v)=Uc(v,t)=U+2g−dv U=m
根据割的定义
c
[
S
,
T
]
=
∑
v
∈
v
‾
′
U
+
∑
u
∈
v
‾
′
(
2
g
−
d
v
+
U
)
+
∑
v
∈
v
′
∑
u
∈
v
‾
′
C
u
,
v
=
U
n
+
2
g
∣
V
′
∣
−
2
∣
E
′
∣
\begin{aligned} c[S,T] &=\sum _{v \in \overline v'} U +\sum _{u \in \overline v'} (2g-dv+U)+\sum _{v\in v'} \sum _{u \in \overline v'} C_{u,v} \\ &=Un+2g|V'|-2|E'| \\ \end{aligned}
c[S,T]=v∈v′∑U+u∈v′∑(2g−dv+U)+v∈v′∑u∈v′∑Cu,v=Un+2g∣V′∣−2∣E′∣
//此处省略一堆证明过程
h
(
g
)
=
g
∣
V
′
∣
+
∣
E
′
∣
=
U
n
−
c
[
S
,
T
]
2
h(g)=g|V'|+|E'|=\frac{Un-c[S,T]}{2}
h(g)=g∣V′∣+∣E′∣=2Un−c[S,T]
由此得出二分判定的条件:
如果h(g)>0 则g偏小,答案在右区间, 如果h(g)<0 则g偏大,答案在左区间
while(r-l>1e-4){
double mid=(r+l)/2;
double t=dinic(mid); //dinic返回的是C[S,T]即最小割的容量
if(U*n-t>0) l=mid; //根据h(g)的正负判断二分
else r=mid;
}
- 求方案
直接从源点S开始搜索,在残留网络中沿着容量大于零的边走,所有遍历到的构成集合S,剩余的构成集合T。集合S即为所求的最大密度子图所包含的点。
!!注意不要从T开始搜,(与反向边无关),集合T不是所求。
void dfs(int u){
st[u]=1;
if(u!=S) ct++;
for(int i=head[u];i;i=e[i].next ){
int v=e[i].to ;
if(e[i].w&&!st[v])
dfs(v);
}
}
- 若此问题拓展到带边权点权:
则图的密度定义为: g = ∑ W e ∑ P v g=\frac{\sum W_e}{\sum P_v} g=∑Pv∑We
通式: c [ S , T ] = U ⋅ n + 2 g ∣ V ′ ∣ − 2 ∑ v ∈ V ′ P v − 2 ∑ e ∈ E ′ W e c[S,T]=U·n+2g|V'|-2\sum_{v\in V'} P_v -2\sum_{e\in E'} W_e c[S,T]=U⋅n+2g∣V′∣−2v∈V′∑Pv−2e∈E′∑We
带边权最大密度子图
- 建图:
{ c ( u , v ) = W e c ( s , v ) = U c ( v , t ) = U + 2 g − d v U = ∑ e ∈ E W e \left\{ \begin{matrix} c(u,v)=W_e \\ c(s,v)=U \\ c(v,t)=U+2g-d_v \\ \end{matrix} \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ U=\sum _{e\in E}W_e \\ \right. ⎩ ⎨ ⎧c(u,v)=Wec(s,v)=Uc(v,t)=U+2g−dv U=e∈E∑We
要注意原图中的边用原图的边权W
此处dv的含义有所改变:表示点v的所有出边的边权和
d v = ∑ ( u , v ) ∈ E W e d_v=\sum _{(u,v)\in E}W_e dv=(u,v)∈E∑We
带点权最大密度子图
- 建图:
{ c ( u , v ) = 1 c ( s , v ) = U c ( v , t ) = U + 2 g − 2 p v − d v U = ∑ v ∈ v ′ P v \left\{ \begin{matrix} c(u,v)=1 \\ c(s,v)=U \\ c(v,t)=U+2g-2p_v-d_v \\ \end{matrix} \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \\ \ \ \ \ \ \ \ \ \\\ \\ \ \ \ \ U=\sum _{v\in v'}P_v \\ \right. ⎩ ⎨ ⎧c(u,v)=1c(s,v)=Uc(v,t)=U+2g−2pv−dv U=v∈v′∑Pv
此处pv的含义:即为点v的点权
点边均带权的最大密度子图
- 建图:
{ c ( u , v ) = W e c ( s , v ) = U c ( v , t ) = U + 2 g − 2 p v − d v U = ∑ v ∈ v ′ P v + ∑ ( u , v ) ∈ E W e \left\{ \begin{matrix} c(u,v)=W_e \\ c(s,v)=U \\ c(v,t)=U+2g-2p_v-d_v \\ \end{matrix} \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ U=\sum _{v\in v'}P_v+\sum _{(u,v)\in E}W_e \\ \right. ⎩ ⎨ ⎧c(u,v)=Wec(s,v)=Uc(v,t)=U+2g−2pv−dv U=v∈v′∑Pv+(u,v)∈E∑We
可以将 2 ( g − P v ) 2(g-P_v) 2(g−Pv)理解为平均点权
总结:
c
[
S
,
T
]
=
U
⋅
n
+
2
g
∣
V
′
∣
−
2
∑
v
∈
V
′
P
v
−
2
∑
e
∈
E
′
W
e
c[S,T]=U·n+2g|V'|-2\sum_{v\in V'} P_v -2\sum_{e\in E'} W_e
c[S,T]=U⋅n+2g∣V′∣−2v∈V′∑Pv−2e∈E′∑We
最小割的表达式通式,其实也就是建图是每个点连向汇点的边权,关键结论记这个就好了
若没有点权则没有
P
v
P_v
Pv那一项 ,若没有边权则
W
e
=
1
W_e=1
We=1
最小权点覆盖集
最小权点覆盖集即能覆盖整个图所有边的点权最小的点集。
注意:覆盖边指的是该边上至少有一个点被选中,而不是要求该边的两个点都被选中。
求一个图的最小权点覆盖集是一个NP完全问题,但在二分图上有较为优美的做法。
二分图中,最小权点覆盖集=最大匹配
一般点权都是非负,如果遇到负点权可以类比在“网络战争”中的做法,加上所有负点权值,再在原图中删去点。
-
建图:
{ c ( u , v ) = ∞ , v ∈ S , u ∈ T c ( s , v ) = w , v ∈ S c ( u , t ) = w , u ∈ T \left\{ \begin{matrix} c(u,v)= \infty, v \in S, u \in T \\ c(s,v)=w , \ \ \ \ \ \ \ \ \ \ \ \ \ v \in S \\ c(u,t)=w , \ \ \ \ \ \ \ \ \ \ \ \ \ u \in T \\ \end{matrix} \\ \right. ⎩ ⎨ ⎧c(u,v)=∞,v∈S,u∈Tc(s,v)=w, v∈Sc(u,t)=w, u∈T
本图中的最小割是简单割 (简单割指与ST点直接相连的割) -
求方案:(注意最小权点覆盖集求方案与前面的都不一样)
第一步:从源点S开始搜索,在残留网络中沿着容量大于零的边走,所有遍历到的构成集合S,剩余的构成集合T。
void ddfs(int u){
st[u]=1;
for(int i=head[u];i;i=e[i].next ){
int v=e[i].to ;
if(!st[v]&&e[i].w){
ddfs(v);
}
}
}
第二步:通过枚举所有正向边,找出所有割边
割边连的点即为最小点覆盖集中的点
for(int i=2;i<=cnt;i+=2){
int a=e[i].to, b=e[i^1].to ;
if(!st[a]&&st[b]) {
if(a==T) printf("%d -\n",b-n);
else if(b==S) printf("%d +\n",a);
}
}
以“有向图破坏”为例。
最大独立点集
最大独立点集即任意两点之间在图中没有对应边的点集
最大独立点集和最小权点覆盖集是一组对偶问题,最小权点覆盖集的补集与最大独立点集一一对应。
最大独立集数=总点数-最小权点覆盖集=总点数-最大匹配
- 求方案
先求最小点覆盖集,它的补集即为最大独立点集。
也可以直接搜索,找跟S和T直接相连的点且他们之间的边容量不为零
例题:王者之剑
二分图之最小路径点覆盖
最小路径点覆盖,即在DAG中,最少的能覆盖图中所有点的路径条数。
(虽然是在有向无环图中讨论的,但其实转到无向图中也是一样的)
最小路径点覆盖分为最小路径点覆盖和最小路径重复点覆盖。
最小路径点覆盖=总点数-最大匹配
求最小路径重复点覆盖注意先求传递闭包,再求最小路径覆盖
- 建图:
二分图中注意拆点,一般是将点的入点赋值为2i 出点赋值为2i+1
- 求方案
//在dinic过程中 记录前驱
if(res>0){
pre[v]=u;
}
void find(int t){
for(int i=1;i<=n;i++){
pre[i]=pre[i+n]; //注意二分图拆点在寻找路径时造成的影响
}
for(int i=n;i>=1;i--){
if(st[i]==0){
int x=i,tmp[200]={0},ct=0;
while(x>=1&&x<=n){
st[x]=1;
tmp[++ct]=x; //倒序储存
x=pre[x]; //
}
for(int k=ct;k>=1;k--){ //倒序输出
printf("%d ",tmp[k]);
}
printf("\n");
}
}
}
费用流模型
基于EK算法,将BFS换成SPFA
最小费用跑最短路,最长费用跑最长路,dis[]含义为费用
一般在综合题当中,费用为点或边的权值,而对点边的限制体现在最大流的流量限制
费用流相比最小割较为简单
- 题目要求输出最小费用和最大费用时,可以先跑一遍最小费用,还原容量之后将费用边权全部取反,再跑一遍最小费用并给结果取反,就是最大费用。免去了两遍EK的繁琐。
- 费用流求方案就看题意随机应变好了
从反向边搜,容量大于0的说明正向边有流量,正向边的费用边权×流量就是该边的费用
for(int i=3;i<=cnt;i+=2){
if(e[i].w){
if(e[i].to>=1&&e[i].to<=m&&e[i^1].to>=m+1&&e[i^1].to<=n+m){
printf("%d %d %d %d\n",e[i].to,e[i^1].to,e[i].w,e[i^1].c*e[i].w);
}
}
}