【图论】【网络流】概念总结

9 篇文章 0 订阅
4 篇文章 0 订阅


最大流模型


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 ]=CinCout
    对于每个 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)=δ=cincout
    对于新图流量守恒 有 δ + y = x \delta+y=x δ+y=x
    y + c i n − c o u t = x y+c_{in}-c_{out}=x y+cincout=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 CC=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>tf0
        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=VE

M a x i m i z e                                       h ( g ) = ∣ E ′ ∣ − g ∣ V ′ ∣ Maximize \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ h(g)=|E'|-g|V'| Maximize                                     h(g)=EgV

一般使用二分求解

转化一下
M i n i m i z e                                       h ( g ) = g ∣ V ′ ∣ − ∣ E ′ ∣ Minimize \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ h(g)=g|V'|-|E'| Minimize                                     h(g)=gVE

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)=gVE=g1=g(2dv+2c[v,v])=21(2gdv+c[v,v])=21((2gdv)+c[v,v])

  • 建图
    可考虑以 2 g − d v 2g-dv 2gdv 作为边权 由于该值可能为负,因此考虑给所有边加上一个极大值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+2gdv                                  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]=vvU+uv(2gdv+U)+vvuvCu,v=Un+2gV2∣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)=gV+E=2Unc[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=PvWe

通式: 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]=Un+2gV2vVPv2eEWe

带边权最大密度子图
  • 建图
    { 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+2gdv                                        U=eEWe
    要注意原图中的边用原图的边权W
    此处dv的含义有所改变:表示点v的所有出边的边权和
    d v = ∑ ( u , v ) ∈ E W e d_v=\sum _{(u,v)\in E}W_e dv=(u,v)EWe
带点权最大密度子图
  • 建图
    { 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+2g2pvdv                            U=vvPv
    此处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+2g2pvdv                    U=vvPv+(u,v)EWe
    可以将 2 ( g − P v ) 2(g-P_v) 2(gPv)理解为平均点权

总结
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]=Un+2gV2vVPv2eEWe
最小割的表达式通式,其实也就是建图是每个点连向汇点的边权,关键结论记这个就好了
若没有点权则没有 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)=,vS,uTc(s,v)=w,             vSc(u,t)=w,             uT
    本图中的最小割是简单割 (简单割指与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);
        }
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值