2022.12.9 闲话

想放个歌词 .

[歌词 1]

[歌词 2]

以上是歌词 .

joke 最近发了一个网络流闲话,我马上就开抄,于是就有了这个闲话 .


最大流和最小割的一些初步应用 . 更多内容:一些没用的初步应用 .

为什么 OJ 的网络流还重构结构了啊?!

没啥用的目录

0. 基本内容 Basic

流网络

一个流网络定义为一张有向图,每条边 (u,v) 对于每条边,有容量 c(u,v) 和流量 f(u,v) .

有两个特殊点:源点 s 和汇点 t .

图要求满足的条件:

  • 如果存在边 (u,v),则不存在边 (v,u),无自环 .
  • 对于每个节点 u,都存在一条路径 suv(所以说图一定连通) .

流要求满足的条件:

  • 流量限制:对于任意节点 u,v,有 0f(u,v)c(u,v) .
  • 流量守恒:对于非源汇点的所有节点 u,有

    vf(v,u)=vf(u,v)

称一个流 f 的值 |f|

|f|=vf(s,v)vf(v,s)

通常来说,源点 s 的入度为 0,于是后面那个 也就为 0 .

需要解决的问题

最大流:给一个流网络 G,问 |f| 最大的流 f 是什么 .

最小费用最大流:一个流网络 G,每条边同时有边权(费用),要求一条流 f 满足:

  • |f| 最大 .
  • 边权和(费用和)最小 .

上下界网络流:以上问题的流量 f 存在上界 / 下界限制 .


最小割(其实不太算网络流):一个流网络 G,删掉若干条边使得 s,t 不连通,同时流量和最小 .

需要注意的,最小流这种是平凡的(0),最大割则是 NPC 问题 .

一些基本转化

反向边去除

注意到流网络有一个限制是如果存在边 (u,v),则不存在边 (v,u) .

那么我们的图就是有反向边怎么办呢?

解决方案也很简单,令 (u,v) 是一组点满足存在边 (u,v),(v,u),且容量分别为 c1,c2 .

则删掉 (v,u),建立一个虚点 v,连 (v,v),(v,u),容量均为 c2 即可 .

易证最大流不变 .

多源汇合一

解决多源点多汇点的问题,只需要建一个超级源点和超级汇点,分别跟源汇点连容量 + 的边即可 .

1. 最大流 Maxflow

最大流的增广路算法

或者叫 Ford-Fulkerson 方法 .

FF, EK, Dinic

不想详细写了,丢个 教程 .

首先建反向边方便操作 .

简要概括一下:

  • FF:每次 DFS 随便找一个增广路然后增广 .
  • EK:先 BFS 找一下,每次增广最短的增广路 .
  • Dinic:分层删掉没用的边然后按层增广(时间复杂度分析见 8.12 闲话
    • 当前弧优化:增广过的边就删掉
    • 无用点优化:不会产生贡献的点删掉
    • Capacity scaling:先不加反向边跑一遍,然后一次性把反向边都加进去,然后再跑一遍 .
    • 26-5:二进制分组,按位 Dinic,然后合起来 .

下面的代码是 Dinic + 当前弧优化 + 无用点优化,可以通过洛谷 & LOJ 的弱化版板子 .

Dinic 代码实现

LOJ 提交记录

// 点数 N
int n, m, s, t;
struct dinic
{
	static const ll INF = 0x3f3f3f3f3f3f3f3f;
	struct Node
	{
		int u, v; ll w;
		Node(int p, int q, ll r) : u(p), v(q), w(r){}
	};
	vector<int> g[N];
	vector<Node> e;
	inline void addedge(int u, int v, ll w)
	{
		int m = e.size();
		e.emplace_back(Node(u, v, w)); g[u].emplace_back(m);
		e.emplace_back(Node(v, u, 0)); g[v].emplace_back(m+1);
	}
	int cur[N], depth[N];
	inline bool bfs(int s, int t)
	{
		memset(cur, 0, sizeof cur);
		memset(depth, -1, sizeof depth);
		queue<int> q; q.push(s); depth[s] = 0;
		while (!q.empty())
		{
			int u = q.front(); q.pop();
			for (auto ee : g[u])
			{
				int v = e[ee].v;
				if (!~depth[v] && e[ee].w){q.push(v); depth[v] = depth[u] + 1;}
			}
		}
		return ~depth[t];
	}
	ll dfs(int u, int t, ll flow)
	{
		if ((u == t) || (flow <= 0)) return flow;
		ll ans = 0; int _ = g[u].size();
		for (int& p = cur[u]; p < _; p++)
		{
			int ee = g[u][p], v = e[ee].v;
			if (depth[u] + 1 != depth[v]) continue;
			ll nxt = dfs(v, t, min(flow, e[ee].w));
			e[ee].w -= nxt; e[ee ^ 1].w += nxt;
			ans += nxt; flow -= nxt;
			if (flow <= 0) break; // 这个如果写在循环条件上会在判断前做一次 p++ 导致漏一条边
		}
		if (ans <= 0) depth[u] = -1;
		return ans;
	}
	ll maxflow(int s, int t)
	{
		ll ans = 0;
		while (bfs(s, t)) ans += dfs(s, t, INF);
		return ans;
	}
};

ISAP

占位 .

实际上我也不打算写,占位的原因是一个 h2 下面只有一个 h3 有点奇怪 .

根据相关法律法规,网络流题是不会卡 Dinic 的,但是 ISAP 是可以卡的,惨案:->Click Here<- .


最大流问题的 HLPP 算法可以做到稳定 O(n2m1/2) 最大流 .

最近(可能也不算最近了)的一篇 paper 提出了 O(m1+o(1)) 的最大流和最小费用最大流算法,可以看作是线性的网络流了,论文链接 .

例题

更难的模型我不会!

朴素例题

蜥蜴

一个 rc 列的网格图,每个地方 (i,j) 有高度 hi,j,一些石柱上有蜥蜴 .

每个蜥蜴每次可以跳到欧几里得距离不超过 d 的任何一个格上 . 每当蜥蜴跳跃时,所离开的格高度减 1,不能跳到高度为 0 的格子上,多个蜥蜴不能同处一格 .

蜥蜴跳到地图外即逃离,问无法逃离的蜥蜴总数的最小值 .

1r,c201d4 .

即求能够逃离的蜥蜴的最大值,最大流即可 .

建图就是把每个点拆成入点和出点,从入点到出点连边,容量为高度,这样就限制了经过次数 .

发现源汇点很多,建虚源汇点即可,注意因为每个地方只有一个蜥蜴所以源点连的权值是 1 .

没了 .

Code
// dinic 略
dinic F;
int n, m, d;
int h[L][L];
bool can[L][L];
inline int dist(int x1, int y1, int x2, int y2){return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);}
inline int tin(int x, int y){return (x-1) * m + y;}
inline int tout(int x, int y){return tin(x, y) + n * m;}
inline bool corner(int x, int y){return min(min(x, n+1-x), min(y, m+1-y)) <= d;}
inline bool cann(int x1, int y1, int x2, int y2)
{
	if ((x1 == x2) && (y1 == y2)) return false;
	return h[x1][y1] && h[x2][y2] && (dist(x1, y1, x2, y2) <= d * d);
}
string tmp;
int main()
{
	scanf("%d%d%d", &n, &m, &d);
	for (int i=1; i<=n; i++)
	{
		cin >> tmp;
		for (int j=0; j<m; j++) h[i][j+1] = tmp[j] - '0';
	}
	int s = 2 * n * m + 1, t = s + 1, ans = 0;
	for (int i=1; i<=n; i++)
	{
		cin >> tmp;
		for (int j=0; j<m; j++)
		{
			can[i][j+1] = (tmp[j] == 'L');
			if (can[i][j+1]) F.addedge(s, tin(i, j+1), 1), ++ans;
		}
	}
	for (int i=1; i<=n; i++)
		for (int j=1; j<=m; j++)
			if (h[i][j])
			{
				F.addedge(tin(i, j), tout(i, j), h[i][j]);
				if (corner(i, j)) F.addedge(tout(i, j), t, INF);
			}
	for (int i1=1; i1<=n; i1++)
		for (int j1=1; j1<=m; j1++)
			for (int i2= max(1, i1-d); i2 <= min(n, i1+d); i2++)
				for (int j2 = max(1, j1-d); j2 <= min(m, j1+d); j2++)
					if (cann(i1, j1, i2, j2)) F.addedge(tout(i1, j1), tin(i2, j2), INF);
	printf("%lld\n", ans - F.maxflow(s, t));
	return 0;
}

后面的题就不都给代码了 .


二分图最大匹配

给一个二分图,求最大匹配 .

源点 s 向所有左部点连一条容量为 1 的边,所有右部点向汇点 t 也连一条容量为 1的边,于是 st 的最大流显然就是答案 .


奇怪的游戏

一个 n×m 矩阵,初始 (i,j) 有数 ai,j,每次可以选两个相邻数同时加一,问至少操作几次使得所有数一样,无解输出 -1 .

1n,m40max{ai}109 .

题解链接,复杂度分析部分比较麻烦可以看链接的,后面就不分析复杂度了 .

骨牌题考虑黑白染色,设黑白点个数分别为 cB,cW,权值和分别为 sB,sW .

每次操作肯定是恰好操作一个黑点和一个白点,于是设最后所有点都变成了 x,则有 cBxsB=cWxsW,也即 (cBcW)x=sBsW .

讨论:

  1. cBcW0 时 .
  2. cBcW=sBsW=0 时 .
  3. 其他情况无解 .

Case 1. 可以解得 x=sBsWcBcW .

于是只要判断 x 是否能作为一个解即可,具体见后 .

Case 2. 这表明黑白色块数量相等且权值和相等 .

因为黑白色块数量相等所以如果 x 可以作为一个解那么 x+1 必然也可以 .

也就是解有单调性,二分答案即可 . 判断 x 是否能作为一个解的做法如下:

考虑建立虚拟源汇点 s,t

  • s 向所有黑点 (i,j) 连边权为 xai,j 的有向边 .
  • 所有白点 (i,j)t 连边权为 xai,j 的有向边 .
  • 所有黑点 (i,j) 向相邻白点连边权为 + 的有向边 .

因为 xai,j 是需要操作的次数,于是因为黑白色块数量权值和相等,如果有解那么流肯定能从 s 流到黑点流到白点再流到 t .

于是只要判断以 s 为源点,t 为汇点的最大流是否等于所有黑点之权值和即可 .


网络流题的一个常见开端就是二分答案,可以看一下 紧急疏散evacuate .

输出方案

我怎么知道如何输出方案?青蛙。

2. 最小割 Mincut

最大流 - 最小割定理

就是最大流 = 最小割 .

证明略 .

最小割树 / Gomory-Hu Tree

基本原理

还以为是什么很难的算法,原来也不算很难 desu .

我们的目标是求任意两点间最小割(等价于最大流).

最小割树定义:如果一棵树 T 满足对于树上的所有边 (u,v) 满足树上去掉 (u,v) 这条边后产生的两个集合恰好是原图上 (u,v) 的最小割把原图分成的两个集合,且边 (u,v) 的权值等于原图上 (u,v) 的最小割,则 T 是一棵最小割树 .

在当前点集随意选取两个点 u,v,在原图上跑出他们之间的最小割,然后就在树上连一条从 uv,权值为 u,v 间最小割的边,然后递归构造 .

(这个算法被称为 Gusfield 算法,实际上是构造了一个等价流树 / Equivalent Flow Tree,和最小割树差不多).

然后两点 u,v 间最小割就是树上边权最小值,这个有很多求法,就不详细展开了 .

关于正确性:OI 不需要证明 .

时间复杂度是 n 次最大流的复杂度,一般用 Dinic 就是 O(n2m) .

Pumping Stations

给一张 n 个点的带权无向图,求一个排列 {an},最大化

i=1n1mincut(ai,ai+1)

n200m1000 .

施 Gomory-Hu 树,则问题变成一棵树上找一个排列 {an},最大化

i=1n1dist(ai,ai+1)

构造的话找到最小边左右分治即可,OI 不需要证明(?

例题

可以看一下杜教的《最小割模型在信息学竞赛中的应用》.

切糕模型

切糕模型,或者叫「最小割离散变量模型」(词源 姜志豪《网络流的一些建模方法》(NOI2016 候选队论文))

切糕

经过千辛万苦小 A 得到了一块切糕,切糕的形状是长方体,小 A 打算拦腰将切糕切成两半分给小 B . 出于美观考虑,小 A 希望切面能尽量光滑且和谐 . 于是她找到你,希望你能帮她找出最好的切割方案 .

出于简便考虑,我们将切糕视作一个长 P、宽 Q、高 R 的长方体点阵 . 我们将位于第 z 层中第 x 行、第 y 列上的点称 (x,y,z),它有一个非负的不和谐值 v(x,y,z) . 一个合法的切面满足以下两个条件:

  • 与每个纵轴(一共有 P×Q 个纵轴)有且仅有一个交点 . 即切面是一个函数 f(x,y),对于所有 (x,y)x[1,P]y[1,Q]),我们需指定一个切割点 f(x,y),且 1f(x,y)R .
  • 切面需要满足一定的光滑性要求,即相邻纵轴上的切割点不能相距太远 . 对于所有的 1x,xP1y,yQ,若 |xx|+|yy|=1,则 |f(x,y)f(x,y)|D,其中 D 是给定的一个非负整数 .

可能有许多切面 f 满足上面的条件,小 A 希望找出总的切割点上的不和谐值最小的那个 .

P,Q,R40DR .

首先把点化成边 .

如果没有高度限制那么显然直接拉 P×Q 条链然后超级源汇点随便最小割一下即可,下面考虑有限制怎么做:

假设我们建出了这样的两条链:

1

2

3

4

5

6

7

8

s

t

然后如果高度差限制是 1,则我们割这样的两条边是不合法的:

1

2

3

4

5

6

7

8

s

t

我们加一条 边(如下图虚线):

1

2

3

4

5

6

7

8

s

t

那么我们发现,这种情况变得不合法了: st 依然连通.

这种建 边来限制选取的方法就叫做「切糕模型」.

广义的,我们用链的形式来表示偏序关系形式的选取限制,然后用最小割解决问题的模型叫做「切糕模型」,也即「最小割离散变量模型」.

(例子来源:rvalue 的博客

有一个正确性证明:dengyaotriangle,虽然我觉得这个建图方式非常明了啊,但是这个正确性证明也可以参考一下 .

建图方式代码(建议全屏观看):

Code
for (int i=1; i<=p; i++)
	for (int j=1; j<=q; j++)
	{
		F.addedge(s, node(i, j, 1), v[i][j][1]); F.addedge(node(i, j, r), t, INF);
		for (int k=2; k<=r; k++) F.addedge(node(i, j, k-1), node(i, j, k), v[i][j][k]);
	}
for (int i=1; i<=p; i++)
	for (int j=1; j<=q; j++)
		for (int k=1; k<=r-d; k++)
		{
			if (inrange(i+1, 1, p)) F.addedge(node(i+1, j, k+d), node(i, j, k), INF);
			if (inrange(i-1, 1, p)) F.addedge(node(i-1, j, k+d), node(i, j, k), INF);
			if (inrange(j+1, 1, q)) F.addedge(node(i, j+1, k+d), node(i, j, k), INF);
			if (inrange(j-1, 1, q)) F.addedge(node(i, j-1, k+d), node(i, j, k), INF);
		}

比较坑的是如果读入每次读入 v(i,j,k) 的循环顺序是 k,i,j .

关于这个模型的一些描述:最小割离散变量模型 .


一些其他题:

最大权闭合子图

最大权闭合子图:

给一张有向图,每个点有点权,选一个子图,使得每个点的所有邻接点都在图内,问最大点权和 .

做法就是加超级源汇 s,ts 连点权是正的点,t 连点权是负的点,容量为点权绝对值,原图的边连容量 + 的边然后最大流即可 .

例题:NOI2006 最大获利 .

多选一模型

不太好说,放个例题(happiness):

这个是二选一的例子,考虑首先加上所有喜悦值然后减最小割 .

建源汇点 s,t,首先连 s 到每个点,容量是文科价值,连每个点到 t,容量是理科价值 .

对于每条边拆出来一个点 x,连 sx,容量为边权,然后 x 对于边到两端连无穷容量的边 .

观察可得是正确的 .

一些例子:文理分科,小 M 的作物,善意的投票等 .

posted @   yspm  阅读(132)  评论(5编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
历史上的今天:
2020-12-09 平衡树
😅​
点击右上角即可分享
微信分享提示