Day4 网络流与二分图

之前那篇博客是在入门网络流时写的,现在对网络流重新有了一定的理解。

学习笔记

1. 最大流

【模板】最大流

FF 增广思想

Ford–Fulkerson 增广,核心即不断找增广路并增广。

dfs 实现

// dfs : 流入 u 的流量为 sum, 求流出的最大流
int dfs(int u, int sum) 
{
	if(sum == 0 or u == t) return sum;
	
	vis[u] = true;
	
	int flow = 0;
	for(int v = 1; v <= n; v++)
	{
		if(!vis[v] and r[u][v] > 0)
		{
			//找 u 到 t 路上的最小限制并增广
			int delta = dfs(v, min(sum, r[u][v]));
			r[u][v] -= d, r[v][u] += d;
			flow += delta, sum -= delta;
		}
	}
	return flow;
}

FF brute code

这种方法的复杂度与最大流 f f f 有关,至多执行 f f f 轮 dfs,时间复杂度 O ( n f ) O(nf) O(nf)

bfs 实现 / EK

FF 增广的 bfs 实现也称 EK 算法。

// bfs 寻找增广路
bool bfs()
{
	memset(d, 0, sizeof(d)); memset(pre, 0, sizeof(pre));
	
	d[s] = 1, q.push(s); 
	
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		for(int v = 1; v <= n; v ++)
			if(d[v] == 0 and r[u][v] > 0)
				pre[v] = u, d[v] = d[u] + 1, q.push(v);
	}
	
	return d[t];
}
// 找到增广路后增广
int augment()
{
	int flow = 1e9;
	for(int a=t; a!=s; a=pre[a]) flow = min(flow, r[pre[a]][a]);
	for(int a=t; a!=s; a=pre[a]) r[pre[a]][a] -= flow, r[a][pre[a]] += flow;
	return flow;
}

FF EK code

EK 算法的时间复杂度是 O ( n m 2 ) O(nm^2) O(nm2)

证明:每条边至多做 n 2 \dfrac n 2 2n 次增广路上的关键边。
OI-wiki

Dinic

通过 bfs 找增广路并对图分层标记,每次 dfs 只访问下一层的结点。

同时加上当前弧优化,即不重复访问增广到极限的边,维护第一条有必要尝试的边。
有必要这样做的原因是如果一个节点有大量出边与入边,挨个遍历可能会 O ( ∣ E ∣ 2 ) O(|E|^2) O(E2)
可以这样做的原因是因为每一遍增广过程中,遍历出边的顺序都是一样的。

注意在流入 u u u 的流量为 0 0 0 时要退出,否则会导致当前弧优化的出错。

时间复杂度 O ( n 2 m ) O(n^2m) O(n2m)

bool bfs()
{
	memset(d, 0, sizeof(d)); 
	d[s] = 1, q.push(s); 
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		for(int i=head[u]; i; i=e[i].nxt)
		{
			int v = e[i].to;
			if(d[v] == 0 and e[i].r > 0)
				d[v] = d[u] + 1, q.push(v);
		}
	}
	return d[t];
}
int dfs(int u, int sum) // dfs 流入 u 的流量为 sum
{
	if(sum == 0 or u == t) return sum;
	
	int flow = 0;
	
	for(int i=cur[u]; i; i=e[i].nxt) 
	{
		cur[u] = i; // 当前弧优化
		int v = e[i].to;
		if(d[v] == d[u] + 1 and e[i].r > 0)
		{
			int d = dfs(v, min(sum, e[i].r));
			e[i].r -= d, e[i ^ 1].r += d;
			flow += d, sum -= d;
		}
	}
	
	return flow;
}

Dinic code

2. 二分图最大匹配

飞行员配对方案问题

  • 二分图:对于 G ( V , E ) G(V,E) G(V,E),分成两个点集 V 1 , V 2 V1,V2 V1,V2 ,对于所有的 ( u , v ) ∈ E (u,v) \in E (u,v)E, 保证 u , v u,v u,v 属于不同点集。

    容易发现二分图中不存在奇环。(走偶数步才能回到某一点集)

  • 二分图的匹配:选定一些边,这些边之间没有公共点。

建网络流模型即可,通过虚拟源点和虚拟汇点限制 1 1 1

如果要输出方案,可以通过 f ( u , v ) < c ( u , v ) f(u,v) < c(u,v) f(u,v)<c(u,v)判断。如果根据残量网络,需要根据反向边的残量网络判断。

代码

3. 最大权闭合子图

分成正点和负点,把不选正点看作损失,每一种割对应一种方案代表不选。

答案即为正收益减去最小损失,即最小割,根据最小割最大流,即最大流。

具体可以看我之前的博客 浅谈网络流

4. 费用流

【模板】最小费用最大流

既然是最大流的前提上费用最小费用,用到的思想一定是基于 FF 增广思想上的。

SSP 算法

贪心,在每次增广中贪心的选择单位流量花费最小的增广路增广。考虑到负权,使用 SPFA 实现。

原图有负环时,最小费用不存在。若无负环,则图中不可能存在负环。

原因:考虑归纳法。

如果当前残量网络上无负环,则增广后同样无负环。若负环由原图上正环的反向边组成,由于找的是最短路,不可能经过原图上正环的所有边;若负环由一部分正向边一部分反向边组成,先遍历的一定是相对较短的,则形成的环非负。

时间复杂度 O ( n m f ) O(nmf) O(nmf) f f f 为最大流。

基于 EK 的实现代码

基于 Dinic 的实现代码

5. 上下界网络流

无源汇有上下界可行流

无源汇有上下界可行流

是否存在一种可行流使得每个点都满足流量平衡,对于边 ( u , v ) (u,v) (u,v),有限制 l ( u , v ) ≤ f ( u , v ) ≤ r ( u , v ) l(u,v) \le f(u,v) \le r(u,v) l(u,v)f(u,v)r(u,v)

设计 f ′ ( u , v ) = f ( u , v ) − l ( u , v ) f'(u,v)=f(u,v)-l(u,v) f(u,v)=f(u,v)l(u,v) 0 ≤ f ′ ( u , v ) ≤ r ( u , v ) − l ( u , v ) 0 \le f'(u,v) \le r(u,v) - l(u,v) 0f(u,v)r(u,v)l(u,v),即一般的网络流模型。

此时 f ′ f' f 关于点 u u u 并不满足流量平衡。

根据 f f f 的流量平衡可推出 f ′ f' f 应该满足 ∑ x → u l ( x , u ) + ∑ x → u f ′ ( x , u ) = ∑ u → y l ( u , y ) + ∑ u → y f ′ ( u , y ) \sum_{x \to u} l(x,u)+\sum_{x \to u} f'(x,u) = \sum_{u \to y} l(u,y)+\sum_{u \to y} f'(u,y) xul(x,u)+xuf(x,u)=uyl(u,y)+uyf(u,y)

记虚拟源点为 s s s,虚拟汇点为 t t t,建 s s s u u u 容量为 ∑ x → u l ( x , u ) \sum_{x \to u} l(x,u) xul(x,u) 的边,建 u u u t t t 容量为 ∑ u → y l ( u , y ) \sum_{u \to y} l(u,y) uyl(u,y) 的边。

跑最大流,有解当且仅当 ∑ f ( s , u ) = ∑ u ( ∑ x → u l ( x , u ) ) \sum f(s,u) = \sum_u (\sum_{x \to u} l(x,u)) f(s,u)=u(xul(x,u))。相当于问存不存在一种方案, f ( s , u ) f(s,u) f(s,u) 都能达到容量限制,此时一定是最大流。如果最大流不能满足,那么一定无解.

求方案根据 f ′ f' f f f f 即可。

代码

有源汇有上下界可行流

有源汇时,考虑到只有源点与汇点不满足流量平衡,由汇点向源点连一条 [ 0 , inf ⁡ ] [0,\inf] [0,inf] 的边,转为无源汇上下界可行流。

有源汇有上下界最大流

有源汇有上下界最大流

SOL1:二分最大流 f f f,由汇点向源点连一条 [ f , inf ⁡ ] [f,\inf] [f,inf] 的边,转为无源汇上下界可行流,时间复杂度 O ( n 2 m log ⁡ V ) O(n^2m \log V) O(n2mlogV)

SOL2:找到一条可行流,在可行流的残量网络上跑 S S S T T T 的最大流,两者相加。


题目

清理雪道

DAG 上的最小边覆盖问题。

本题题解

CF1783F Double Sort II

错排建图的套路。

先只考虑一个数列:记 i i i 所在位置 p i p_i pi,将 i → p i i \to p_i ipi 建边,形成若干个环,每次操作可以让环上少一个点,因此最小操作次数为 n n n 减去环的数量。

如果从反面出发,想最多能保留几个点可以不动,省操作次数,能保留的数量就是环的数量。

考虑两个数列:对于一个环,最多被省一个点。对于一个点,最多被省一次。

i i i 所在的两个环建边跑二分图最大匹配即可。

CF1766F MCF

最小费用有源汇可行流,但有一个流量与容量奇偶性相同的限制。

对于奇数边有下界 f ( u , v ) ≥ 1 f(u,v) \ge 1 f(u,v)1,给他们制定下界,先让他们流入 1 1 1 的容量。然后我们令 f ′ ( u , v ) = f ( u , v ) 2 , c ′ ( u , v ) = c ( u , v ) 2 f'(u,v) = \dfrac {f(u,v)} {2},c'(u,v) =\dfrac {c(u,v)} {2} f(u,v)=2f(u,v)c(u,v)=2c(u,v)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值