2022.12.9 闲话

想放个歌词 .

[歌词 1]

[歌词 2]

以上是歌词 .

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


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

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

没啥用的目录

0. 基本内容 Basic

流网络

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

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

图要求满足的条件:

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

流要求满足的条件:

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

    \[\sum_vf(v,u)=\sum_vf(u,v) \]

称一个流 \(f\) 的值 \(|f|\)

\[|f| = \sum_vf(s,v) - \sum_vf(v,s) \]

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

需要解决的问题

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

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

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

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


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

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

一些基本转化

反向边去除

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

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

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

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

易证最大流不变 .

多源汇合一

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

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(n^2m^{1/2})\) 最大流 .

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

例题

更难的模型我不会!

朴素例题

蜥蜴

一个 \(r\)\(c\) 列的网格图,每个地方 \((i,j)\) 有高度 \(h_{i,j}\),一些石柱上有蜥蜴 .

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

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

\(1\le r,c\le 20\)\(1\le d\le 4\) .

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

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

发现源汇点很多,建虚源汇点即可,注意因为每个地方只有一个蜥蜴所以源点连的权值是 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\)的边,于是 \(s\)\(t\) 的最大流显然就是答案 .


奇怪的游戏

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

\(1\le n,m\le 40\)\(\max\{a_i\}\le10^9\) .

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

骨牌题考虑黑白染色,设黑白点个数分别为 \(c_{\sf B}, c_{\sf W}\),权值和分别为 \(s_{\sf B}, s_{\sf W}\) .

每次操作肯定是恰好操作一个黑点和一个白点,于是设最后所有点都变成了 \(x\),则有 \(c_{\sf B}\cdot x-s_{\sf B}=c_{\sf W}\cdot x-s_{\sf W}\),也即 \((c_{\sf B}-c_{\sf W})x=s_{\sf B}-s_{\sf W}\) .

讨论:

  1. \(c_{\sf B} - c_{\sf W} \neq 0\) 时 .
  2. \(c_{\sf B} - c_{\sf W} = s_{\sf B} - s_{\sf W} = 0\) 时 .
  3. 其他情况无解 .

Case 1. 可以解得 \(x=\dfrac{s_{\sf B}-s_{\sf W}}{c_{\sf B} - c_{\sf W}}\) .

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

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

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

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

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

  • \(s\) 向所有黑点 \((i,j)\) 连边权为 \(x-a_{i,j}\) 的有向边 .
  • 所有白点 \((i,j)\)\(t\) 连边权为 \(x-a_{i,j}\) 的有向边 .
  • 所有黑点 \((i,j)\) 向相邻白点连边权为 \(+\infty\) 的有向边 .

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

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


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

输出方案

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

2. 最小割 Mincut

最大流 - 最小割定理

就是最大流 = 最小割 .

证明略 .

最小割树 / Gomory-Hu Tree

基本原理

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

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

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

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

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

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

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

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

Pumping Stations

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

\[\sum_{i=1}^{n-1}\operatorname{mincut}(a_i,a_{i+1}) \]

\(n\le 200\)\(m\le 1000\) .

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

\[\sum_{i=1}^{n-1}\operatorname{dist}(a_i,a_{i+1}) \]

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

例题

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

切糕模型

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

切糕

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

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

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

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

\(P,Q,R\le 40\)\(D\le R\) .

首先把点化成边 .

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

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

graph LR; s-->1 1-->2 2-->3 3-->4 4-->t s-->5 5-->6 6-->7 7-->8 8-->t

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

graph LR; s-->1 1-->2 2-->3 3==>4 4-->t s-->5 5==>6 6-->7 7-->8 8-->t

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

graph LR; s-->1 1-->2 2-->3 3==>4 4-->t s-->5 5==>6 6-->7 7-->8 8-->t 3-.->6

那么我们发现,这种情况变得不合法了: \(s\)\(t\) 依然连通.

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

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

(例子来源: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,t\)\(s\) 连点权是正的点,\(t\) 连点权是负的点,容量为点权绝对值,原图的边连容量 \(+\infty\) 的边然后最大流即可 .

例题:NOI2006 最大获利 .

多选一模型

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

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

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

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

观察可得是正确的 .

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

posted @ 2022-12-09 15:04  Jijidawang  阅读(121)  评论(5编辑  收藏  举报
😅​