网络流

网络流

不说废话。

概念

网络,对于一张有向连通图 \(G=(V,E)\),其中分别有且仅有一个 \(s \in V,t \in V\),且 \(s\) 无入度,\(t\) 无出度,则我们称 \(G\) 是一个网络。其中我们称 \(s\)源点\(t\)汇点

对于一个网络,其需要满足一下性质:

  • 对于一条边 \((u,v) \in E\),定义 \(f(u,v)\) 为其流量\(c(u,v)\) 为其容量,且要求 \(f(u,v) \le c(u,v)\)

  • 对于一个点 \(u \in V\),称其净流量为其总流入总流出,且除了 \(s\)\(t\) 外,要求其流入等于其流出,即对于任意 \(u \ne {s,t}\)\(u\) 的净流量为 \(0\)

定义一个边 \((u,v)\)剩余容量为其 \(f(u,v)-c(u,v)\) 即容量减流量,记作 \(c_f(u,v)\)。定义剩余流量不为 \(0\) 的边和节点构成的子图为残量网络。记作 \(G_f\)

不难发现,\(s\) 的流出等于 \(t\) 的流入。我们称整个网络的流为 \(s\) 的流出即 \(t\) 的流入。

我们把 \(G_f\) 中一条 \(u\)\(v\) 的路径称作增广路

我们对于一条增广路,给每一条边都加上一个相等的流量,我们称这个过程为增广

另外,反向边为一条 \(G\) 中一条边的反向边即 \((v,u)\),且 \(f(v,u) = -f(u,v)\)

最大流

对于一个网络,找到一个流,使得流的流量最大。

板子题:

P3376 【模板】网络最大流

给定一个网络以及其源点、汇点,求其网络最大流。

\(1 \le n \le 200\), \(1 \le m \le 5000\)\(0 \le w \le 2^{31}\)

FF 算法

FF 算法即 Ford-Fulkerson 算法。

首先考虑最暴力的策略。每次只要找到一条增广路,便对他进行增广。重复此流程,直到网络中不存在增广路。

显然,这个是假的贪心。随意找一个例子就可以卡掉。

我们拿下面的这幅图举例子(如图 1,图中边权标注的是剩余容量)。首先我们找到一条增广路 \(1 \rightarrow 2 \rightarrow 3 \rightarrow 4\) 并且对他进行增广,这些边剩余容量变为 \(0\)。然后图中不存在其余增广路(如图 2),最终求得此网络的流为 \(1\)

o_260111075927_dfkajijhah.png (962×473) (cnblogs.com)

然而,不难发现,如果我们对于 \(1 \rightarrow 3 \rightarrow 4\)\(1 \rightarrow 2 \rightarrow 4\) 分别进行增广,得到的流量为 \(2\)

为了解决这样的问题,我们引入反向边,反向边为一条 \(G\) 中一条边的反向边即 \((v,u)\),且我们约定 \(f(v,u) = -f(u,v)\)。为了满足这样的性质,在对 \((u,v)\) 的流量加 \(val\) 时,需要给其反向边的流量减 \(val\)

以此,我们可以借助反向边解决问题。(如图 3)

我们不妨将反向边当作正常的边进行处理。

o_260111081300_faudsiafu.png (1064×380) (cnblogs.com)

我们还是正常找一条增广路进行增广,然后这条增广路上的边的流量增加,同时其反向边的流量相应的减少,其剩余流量自然跟随变化(如图 4)。但是此时,我们在进行以此增广之后,你就发现你还可以找到一条增广路 \(1 \rightarrow 3 \rightarrow 2 \rightarrow 4\),对他进行增广。进行完这些操作后,得到最大流量 \(2\)(如图 5)。

仔细观察不难发现,第二次增广时,当 \((2,3)\) 的反向边被增广后,\((2,3)\) 相当于没有被增广过。所以,反向边相当于一个“撤销”操作。

这就是 FF 算法的核心思想。可以使用 DFS 实现。但是时间复杂度过高。下面考虑优化。

EK 算法

EK 算法即 Edmonds-Karp 算法。核心思想在于利用 bfs 进行 FF 增广。

大致流程如下:

  1. \(G_f\) 上找一条增广路。
  2. 在增广路上求出剩余容量的最小值,然后给增广路上边的流量加上他,给对应的反向边的容量减去他。
  3. 重复 1、2,直到没有增广路。

依此,便可以求出该网络的最大流量。单轮增广的时间复杂度为 \(O(E)\),增广轮数理论上界为 \(O(VE)\),那么 EK 算法总的时间复杂度为 \(O(VE^2)\)

Dinic 算法

Dinic 算法是求解最大流问题的主流算法。是对于 FF/EK 的优化。

对于每一次增广,首先 bfs 对图按照 \(dis(s,u)\) 进行分层。我们让每个点 \(u\) 只能向自己的下一层的点 \(v\) 进行增广。这样每次增广的都是原图中边数最少的路径,且可以保证不会因为反向边的缘故让接下来的 dfs 陷入死循环。

分层之后,从 \(s\) 开始 dfs。区别于 EK,我们对于一个点 \(u\),如果他从他的一个儿子找到了增广路,不必回到 \(s\) 进行再次处理,可以直接继续从 \(u\) 进行增广。

然后我们就可以写出复杂度较为正确的求最大流代码。

点击查看代码
#include <bits/stdc++.h>
#define il inline
#define int long long

using namespace std;

bool Beg;
namespace Zctf1088 {
namespace IO {
	const int bufsz = 1 << 20;
	char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
	#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
	il int read() {
		int x = 0; char ch = getchar(); bool t = 0;
		while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
		while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
		return t ? -x : x;
	}
	char obuf[bufsz], *p3 = obuf, stk[50];
	#define flush() (fwrite(obuf, 1, p3 - obuf, stdout), p3 = obuf)
	#define putchar(ch) (p3 == obuf + bufsz && flush(), *p3++ = (ch))
	il void write(int x, bool t = 0) {
		int top = 0;
		x < 0 ? putchar('-'), x = -x : 0;
		do {stk[++top] = x % 10 | 48; x /= 10;} while(x);
		while (top) putchar(stk[top--]);
		t ? putchar(' ') : putchar('\n');
	}
	struct FL {
		~FL() {flush();}
	} fl;
}
using IO::read; using IO::write;
const int N = 205, M = 1e4 + 10;
const int INF = 0x3f3f3f3f3f3f3f3f;
int n, m, S, T;
struct Edge {
	int nxt, to, w;
} edge[M];
int head[M], tot = 1;
il void addEdge(int x, int y, int w) {
	edge[++tot] = {head[x], y, w};
	head[x] = tot;
}
int dis[N];
queue<int> q;
il bool bfs() {
	for (int i = 1; i <= n; i++) dis[i] = 0;
	while (!q.empty()) q.pop();
	q.push(S);
	dis[S] = 1;
	while (!q.empty()) {
		int x = q.front(); q.pop();
		for (int i = head[x]; i; i = edge[i].nxt) {
			int y = edge[i].to, w = edge[i].w;
			if (w > 0 && dis[y] == 0) {
				dis[y] = dis[x] + 1;
				if (y == T) return true;
				q.push(y);
			}
		}
	}
	return false;
}
il int dfs(int x, int flow) {
	if (x == T) return flow;
	int rest = flow;
	for (int i = head[x]; i; i = edge[i].nxt) {
		int y = edge[i].to, w = edge[i].w;
		if (w > 0 && dis[y] == dis[x] + 1) {
			int k = dfs(y, min(rest, w));
			rest -= k;
			edge[i].w -= k;
			edge[i ^ 1].w += k;
		}
	}
	return flow - rest;
}
il int dinic() {
	int res = 0;
	while (bfs()) res += dfs(S, INF);
	return res;
}
signed main() {
	n = read(), m = read(), S = read(), T = read();
	for (int i = 1; i <= m; i++) {
		int x = read(), y = read(), w = read();
		addEdge(x, y, w);
		addEdge(y, x, 0);
	}
	write(dinic());
    
	return 0;
}}
bool End;
il void Usd() {cerr << "\nUse: " << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n";}
signed main() {
	Zctf1088::main();
	Usd();
	return 0;
}

但是这篇代码依然无法通过最大流板子题。

我们需要一个优化:当前弧优化

考虑对于一个节点,从他的一个儿子走下去找到了增广路回来后,一定是一下两种情况之一:

  1. 当前节点的流入还有剩余,说明从这个儿子走下去已经没有剩余容量了,从这个儿子走下去已经被填满了。那么以后就没有必要再走这个节点了。
  2. 当前节点的流入没有剩余,说明从这个儿子走下去可能还有剩余,但是后面的儿子给到的流出一定是 \(0\),因为当前节点已经没有剩余的流了。直接 break 掉即可。

无论是以上两种情况,我们都会发现,我们再次到这个节点时,肯定就不需要再走这个儿子之前的儿子了,因为他们肯定已经被处理完了。那么我们每次走到 \(u\) 的一个儿子 \(v\),就令 \(cur_u=v\),下一次就不从 \(head_u\) 开始遍历,而是 \(cur_u\)

那么就考虑在实现 dinic 时加上面三条优化。

于是我们 dinic 的复杂度就可以得到保证。时间复杂度为 \(O(V^2E)\)

点击查看代码
#include <bits/stdc++.h>
#define il inline
#define int long long

using namespace std;

bool Beg;
namespace Zctf1088 {
namespace IO {
	const int bufsz = 1 << 20;
	char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
	#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
	il int read() {
		int x = 0; char ch = getchar(); bool t = 0;
		while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
		while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
		return t ? -x : x;
	}
	char obuf[bufsz], *p3 = obuf, stk[50];
	#define flush() (fwrite(obuf, 1, p3 - obuf, stdout), p3 = obuf)
	#define putchar(ch) (p3 == obuf + bufsz && flush(), *p3++ = (ch))
	il void write(int x, bool t = 0) {
		int top = 0;
		x < 0 ? putchar('-'), x = -x : 0;
		do {stk[++top] = x % 10 | 48; x /= 10;} while(x);
		while (top) putchar(stk[top--]);
		t ? putchar(' ') : putchar('\n');
	}
	struct FL {
		~FL() {flush();}
	} fl;
}
using IO::read; using IO::write;
const int N = 205, M = 1e4 + 10;
const int INF = 0x3f3f3f3f3f3f3f3f;
int n, m, S, T;
struct Edge {
	int nxt, to, w;
} edge[M];
int head[M], tot = 1, cur[M];
il void addEdge(int x, int y, int w) {
	edge[++tot] = {head[x], y, w};
	head[x] = tot;
}
int dis[N];
queue<int> q;
il bool bfs() {	// 判断有无增广路并对图进行分层
	for (int i = 1; i <= n; i++) dis[i] = 0, cur[i] = head[i];
	while (!q.empty()) q.pop();
	q.push(S);
	dis[S] = 1;
	while (!q.empty()) {
		int x = q.front(); q.pop();
		for (int i = head[x]; i; i = edge[i].nxt) {
			int y = edge[i].to, w = edge[i].w;
			if (w > 0 && dis[y] == 0) {
				dis[y] = dis[x] + 1;
				if (y == T) return true;
				q.push(y);
			}
		}
	}
	return false;
}
il int dfs(int x, int flow) {	// 上一层传入的流量
	if (x == T) return flow;	// 找到汇点返回
	int rest = flow;	// 当前节点剩余的可分配流量
	for (int i = cur[x]; i && rest; i = edge[i].nxt) {
		cur[x] = i;	// 当前弧优化
		int y = edge[i].to, w = edge[i].w;
		if (w > 0 && dis[y] == dis[x] + 1) {// 还有剩余流量在下一层
			int k = dfs(y, min(rest, w));	// 计算下面层能经过的流量
			if (k == 0) dis[y] = 0;
			rest -= k;
			edge[i].w -= k;		// 剩余流量减少
			edge[i ^ 1].w += k;	// 反向边剩余流量增加
		}
	}
	return flow - rest;
}
il int dinic() {
	int res = 0;
	while (bfs()) res += dfs(S, INF);	// 重复找增广路并 dfs 的过程直到没有增广路
	return res;
}
signed main() {
	n = read(), m = read(), S = read(), T = read();
	for (int i = 1; i <= m; i++) {
		int x = read(), y = read(), w = read();
		addEdge(x, y, w);
		addEdge(y, x, 0);	// 加反向边
	}
	write(dinic());
	
	return 0;
}}
bool End;
il void Usd() {cerr << "\nUse: " << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n";}
signed main() {
	Zctf1088::main();
	Usd();
	return 0;
}

经过后期写题的调整,有一个比较实用的版本:

点击查看代码
struct Edge {
	int nxt, to, w;
} edge[M];
int head[N], cur[N], etot = 1;
il void addEdge(int x, int y, int w) {
	edge[++etot] = {head[x], y, w};
	head[x] = etot;
}
il void addEdge1(int x, int y, int w) {
	addEdge(x, y, w);
	addEdge(y, x, 0);
}
int dis[N], S, T;
queue<int> q;
il bool bfs(int n) {
	for (int i = 1; i <= n; i++) cur[i] = head[i], dis[i] = 0;
	while (!q.empty()) q.pop();
	dis[S] = 1; q.push(S);
	while (!q.empty()) {
		int x = q.front(); q.pop();
		for (int i = head[x]; i; i = edge[i].nxt) {
			int y = edge[i].to, w = edge[i].w;
			if (w > 0 && !dis[y]) {
				dis[y] = dis[x] + 1;
				if (y == T) return true;
				q.push(y);
			}
		}
	}
	return false;
}
il int dfs(int x, int flow) {
	if (x == T) return flow;
	int rest = flow;
	for (int i = cur[x]; i && rest; i = edge[i].nxt) {
		cur[x] = i;
		int y = edge[i].to, w = edge[i].w;
		if (w > 0 && dis[y] == dis[x] + 1) {
			int k = dfs(y, min(rest, w));
			if (k == 0) dis[y] = 0;
			rest -= k;
			edge[i].w -= k;
			edge[i ^ 1].w += k;
		}
	}
	return flow - rest;
}
il int dinic(int n, int s, int t) {
	S = s, T = t;
	int res = 0;
	while (bfs(n)) res += dfs(S, INF);
	return res;
}

最小割

定义

对于一个网络 \(G\),将其划分为两个点集 \(S,T\),且 \(s \in S,t \in T\),则称 \(\{S,T\}\)\(G\) 的一个

定义割 \(\{S,T\}\)容量\(\sum_{u \in S} \sum_{v \in T} c(u,v)\)最小割即求这个容量的最小值。

最大流最小割定理

内容

对于一个网络,其最大流 \(f\),和其最小割 \(\{S,T\}\),有 \(|f|=||\{S,T\}||\)

其中,\(|f|\) 为流 \(f\) 的流量,\(||\{S,T\}||\) 为割 \(\{S,T\}\) 的容量即 \(\sum_{u \in S} \sum_{v \in T} c(u,v)\)

即:最大流 = 最小割

证明

证明两个引理。

引理 1

  • 对于任意流 \(f\) 与割 \(\{S,T\}\),有 \(|f| \le ||S,T||\)

其中取等的情况当且仅当 \(\{(u,v),u\in S,v\in T\}\) 都是满流,且 \(\{(u,v),u\in T,v\in S\}\) 都是空流,即从 \(S\)\(T\) 的边都是满流,从 \(T\)\(S\) 的边都是空流。

下面的 \(f(x)\)\(x\) 的净流量,\(f(x,y)\) 为边 \((x,y)\) 为的流量。

证明:

\[\begin{aligned} |f| & =f(s) \\ & =\sum_{u \in S} f(u) \\ & =\sum_{u \in S}\left(\sum f(u, v)-\sum f(v, u)\right) \\ & =\sum_{u \in S}\left(\sum_{v \in S} f(u, v)+\sum_{v \in T} f(u, v)-\sum_{v \in S} f(v, u)-\sum_{v \in T} f(v, u)\right) \\ & =\sum_{u \in S}\left(\sum_{v \in T} f(u, v)-\sum_{v \in T} f(v, u)\right)+\sum_{u \in S} \sum_{v \in S} f(u, v)-\sum_{u \in S} \sum_{v \in S} f(v, u) \\ & =\sum_{u \in S}\left(\sum_{v \in T} f(u, v)-\sum_{v \in T} f(v, u)\right) \\ & \leq \sum_{u \in S} \sum_{v \in T} f(u, v) \quad(\text {此时如果要取等就要让 }\{(u, v), u \in T, v \in S\} \text { 都是空流}) \\ & \leq \sum_{u \in S} \sum_{v \in T} c(u, v) \quad(\text {此时如果要取等就要让 }\{(u, v), u \in S, v \in T\} \text { 都是满流}) \\ & =\|S, T\| \end{aligned} \]

引理 1 证毕。

引理 2

  • 对于任意一个网络,总会存在流 \(f\) 和割 \(\{S,T\}\) 使得 \(|f|=||S,T||\)

证明:

考虑给出构造:考虑在一增广得到一个流 \(f\) 后,网络上不存在 \(s \leadsto t\),这时 \(s\) 所在的联通块为 \(S\)\(t\) 所在的联通块为 \(T\),此时正边都被增广完了,反边容量都为 \(0\),则有:\(\{(u,v),u\in S,v\in T\}\) 都是满流,且 \(\{(u,v),u\in T,v\in S\}\) 都是空流。

引理 2 证毕。

那么,对于最大流 \(f'\) 和最小割 \(\{S',T'\}\),有:

  • 由引理 1 得,\(|f'| \le ||S',T'||\)
  • 由引理 2 得,由于 \(|f'| \geq |f|,||S,T|| \geq ||S',T'||\),而又必然存在 \(|f|=||S,T||\),则可得 \(|f'| \geq ||S',T'||\)

于是 \(|f'|=||S',T'||\),最大流最小割定理得证。

最小割树

模板题:

P4897 【模板】最小割树(Gomory-Hu Tree)

给定一张 \(n+1\)(编号 \(0\)\(n\))个点 \(m\) 条边得无向连通图,多次询问两点之间的最小割。

\(1 \le n \le 500\)\(0 \le m \le 500\)\(0 \le Q \le 10^5\)

我们定义两点之间的最小割:原图的每条边有一个割断它的代价,即为使得这两个点不连通的最小代价。

考虑建树。

首先,我们在当前的这张图中,任取两点 \(u,v\),然后跑出他们之间的最小割 \(cut(u,v)\)。我们在新图(即建出来的最小割树)上连接 \(u,v\),边权为 \(cut(u,v)\)

不难发现,图中满流的边就是 \(u,v\) 的割。沿任意一个合法的割将原图分成两部分 \(U,V\),然后分治求解。重复此流程,即可建出最小割树。

最小割树的性质在于:最小割树上的任意两点 \(u,v\),他们在树上简单路径边权的最小值,即为他们在原图上的最小割。

点击查看代码
#include <bits/stdc++.h>
#define il inline
#define int long long

using namespace std;

bool Beg;
namespace Zctf1088 {
namespace IO {
	const int bufsz = 1 << 20;
	char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
	#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
	il int read() {
		int x = 0; char ch = getchar(); bool t = 0;
		while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
		while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
		return t ? -x : x;
	}
	char obuf[bufsz], *p3 = obuf, stk[50];
	#define flush() (fwrite(obuf, 1, p3 - obuf, stdout), p3 = obuf)
	#define putchar(ch) (p3 == obuf + bufsz && flush(), *p3++ = (ch))
	il void write(int x, bool t = 0) {
		int top = 0;
		x < 0 ? putchar('-'), x = -x : 0;
		do {stk[++top] = x % 10 | 48; x /= 10;} while(x);
		while (top) putchar(stk[top--]);
		t ? putchar(' ') : putchar('\n');
	}
	struct FL {
		~FL() {flush();}
	} fl;
}
using IO::read; using IO::write;
const int INF = 0x3f3f3f3f3f3f3f3f;
const int N = 1e5, M = 1e6;
int n, m;
struct node {
	int x, y, w;
} e[M];
struct Edge {
	int nxt, to, w;
} edge[M];
int head[N], cur[N], etot = 1;
il void addEdge(int x, int y, int w) {
	edge[++etot] = {head[x], y, w};
	head[x] = etot;
}
il void addEdge1(int x, int y, int w) {
	addEdge(x, y, w);
	addEdge(y, x, 0);
}
int dis[N], S, T;
queue<int> q;
il bool bfs(int n) {
	for (int i = 1; i <= n; i++) cur[i] = head[i], dis[i] = 0;
	while (!q.empty()) q.pop();
	dis[S] = 1; q.push(S);
	while (!q.empty()) {
		int x = q.front(); q.pop();
		for (int i = head[x]; i; i = edge[i].nxt) {
			int y = edge[i].to, w = edge[i].w;
			if (w > 0 && !dis[y]) {
				dis[y] = dis[x] + 1;
				if (y == T) return true;
				q.push(y);
			}
		}
	}
	return false;
}
il int dfs(int x, int flow) {
	if (x == T) return flow;
	int rest = flow;
	for (int i = cur[x]; i && rest; i = edge[i].nxt) {
		cur[x] = i;
		int y = edge[i].to, w = edge[i].w;
		if (w > 0 && dis[y] == dis[x] + 1) {
			int k = dfs(y, min(rest, w));
			if (k == 0) dis[y] = 0;
			rest -= k;
			edge[i].w -= k;
			edge[i ^ 1].w += k;
		}
	}
	return flow - rest;
}
int vis[N];
il void solve(int x) {	// 将图割为两个部分
	vis[x] = 1;
	for (int i = head[x]; i; i = edge[i].nxt) {
		int y = edge[i].to, w = edge[i].w;
		if (w > 0 && !vis[y]) solve(y);
	}
}
il void init() {
	for (int i = 1; i <= etot; i++) edge[i] = {0, 0, 0};
	for (int i = 1; i <= n; i++) head[i] = vis[i] = 0;
	etot = 1;
	for (int i = 1; i <= m; i++) {
		addEdge1(e[i].x, e[i].y, e[i].w);
		addEdge1(e[i].y, e[i].x, e[i].w);
	}
}
il int dinic(int n, int s, int t) {	// 正常跑最小割(最大流)
	init();
	S = s, T = t;
	int res = 0;
	while (bfs(n)) res += dfs(S, INF);
	solve(S);
	return res;
}
struct edge1 {
	int y, w;
};
vector<edge1> G[N];
int p[N], p1[N], p2[N];
il void build(int l, int r) {	// 建树
	if (l >= r) return;
	int s = p[l], t = p[l + 1];	// 随便挑两个点
	int res = dinic(n, s, t);
	G[s].push_back({t, res});
	G[t].push_back({s, res});
	int tot1 = 0, tot2 = 0;		// 以下部分为分治求解
	for (int i = l; i <= r; i++) {
		if (vis[p[i]]) p1[++tot1] = p[i];
		else p2[++tot2] = p[i];
	}
	for (int i = 1; i <= tot1; i++) p[l + i - 1] = p1[i];
	for (int i = 1; i <= tot2; i++) p[l + tot1 + i - 1] = p2[i];
	build(l, l + tot1 - 1);
	build(l + tot1, r);
}
int dep[N], fa[N][22], mn[N][22];
il void dfs1(int x, int fath) {
	dep[x] = dep[fath] + 1;
	fa[x][0] = fath;
	for (int i = 1; i <= 20; i++) {
		fa[x][i] = fa[fa[x][i - 1]][i - 1];
		mn[x][i] = min(mn[x][i - 1], mn[fa[x][i - 1]][i - 1]);
	}
	for (edge1 i : G[x]) {
		if (i.y == fath) continue;
		mn[i.y][0] = i.w;
		dfs1(i.y, x);
	}
}
il int getlca(int x, int y) {
	if (dep[x] < dep[y]) swap(x, y);
	for (int i = 20; i >= 0; i--) 
		if (dep[fa[x][i]] >= dep[y]) x = fa[x][i];
	if (x == y) return x;
	for (int i = 20; i >= 0; i--) 
		if (fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
	return fa[x][0];
}
il int getkmn(int x, int k) {	// 倍增求解路径最小值
	int res = INF;
	for (int i = 0; i <= 20; i++) {
		if ((k >> i) & 1) {
			res = min(res, mn[x][i]);
			x = fa[x][i];
		}
	}
	return res;
}
il int getmn(int x, int y) {
	int lca = getlca(x, y);
	return min(getkmn(x, dep[x] - dep[lca]), getkmn(y, dep[y] - dep[lca]));
}
signed main() {
	n = read() + 1, m = read();
	for (int i = 1; i <= m; i++) {
		int x = read() + 1, y = read() + 1, w = read();
		e[i] = {x, y, w};
	}
	for (int i = 1; i <= n; i++) p[i] = i;
	build(1, n);
	dfs1(1, 0);
	int qq = read();
	while (qq--) {
		int x = read() + 1, y = read() + 1;
		write(getmn(x, y));
	}
	return 0;
}}
bool End;
il void Usd() {cerr << "\nUse: " << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n";}
signed main() {
	Zctf1088::main();
	Usd();
	return 0;
}

最大权闭合子图

给定一张有向图 \(G\),定义他的一个闭合子图为该有向图的一个点集,满足这个点集中的点的出边指向还是这个点集中的点。

点有点权,求权最大的闭合子图即为最大权闭合子图

最大权闭合子图问题本质上无异于最小割问题。

考虑给这个图加上源点和汇点 \(S,T\),构造网络,具体地:

  • 源点向所有正点权的点连边,容量为点权。
  • 所有负点权的点向汇点连边,容量为点权的绝对值。
  • 原图中的边不变,容量为 \(\infty\)

于是 \(G\) 成为一个网络。

给出结论:最大权闭合子图 = 正点权之和 - 最大流

证明:

注意到,一个闭合子图对应网络中的一个割。于是这便可以转化为一个最小割问题。

割开的图的两部分中,与 \(S\) 相连的点集必然没有边连向与 \(T\) 相连点集,否则会割到 \(\infty\) 的边,显然不是最小割。故最小割一定割在直接与 \(S,T\) 相连的边上。于是在闭合子图中的点就有:与 \(S\) 有连边不断开,与 \(T\) 的连边断开。

可得:闭合子图权值和 = 所选正点权之和 + 所选负点权之和 = 所有正点权之和 - 未选正点权之和 - 所选负点权绝对值之和 = 所有正点权之和 - 割的容量。

正点权之和一定,故当割的容量最小时,闭合子图权值和最大。即:最大权闭合子图 = 正点权之和 - 最小割。

又最大流最小割定理,最大权闭合子图 = 正点权之和 - 最大流。上述结论得证。

平面图和对偶图

概念

对于一张图 \(G(V,E)\),若将其平铺在一个平面上可以使得除了顶点之外各边没有相交,那么 \(G\) 就是一个平面图

在这个平面上,\(G\) 的边会将这个平面划分为若干个区域,每个区域称为 \(G\) 的一个 \(R_i\)

考虑建一张新图。对于一条边 \(e \in E\),且他划分的两个面为 \(R_i\)\(R_j\),那么就从 \(R_i\)\(R_j\) 连一条边,边权与 \(e\) 相同。若 \(R_i=R_j\),则就是 \(R_i\) 的自环。于是我们称这张新图就是 \(G\) 的对偶图。

例:

o_260308091809_ddddddtrrrrre.png (555×470) (cnblogs.com)

平面图最小割

考虑一个 \(s-t\) 平面图,即存在源点和汇点的平面图。考虑从在 \(s\)\(t\) 之间连一条边,这显然不影响他是平面图。

o_260308092050_jfdakadsaer.png (575×561) (cnblogs.com)

于是有:平面图上的最小割 = 对偶图上的最短路

例题:P4001 [ICPC 2006 Beijing R] 狼抓兔子

此题显然就是裸的最小割,但是对于 \(1 \times 10^6\) 级别的点数和边数 dinic 比较吃力。考虑转化为对偶图求解。

费用流

概述

给定网络 \(G\),每条边上除了有容量限制 \(c(u,v)\),还有一个单位流量的费用 \(w(u,v)\)。即当一条边的流量为 \(f(u,v)\) 时,需要花费 \(f(u,v) \times w(u,v)\) 的代价。

费用流的问题即为:在满足最大流的情况下,求最大/小费用,称最大/小费用最大流问题。二者便统称为费用流问题

板子题:

P3381 【模板】最小费用最大流

给定网络 \(G\),求其最小费用最大流。

SPFA 费用流

所求无非是在最大流的情况下的最小费用。

使用类似求最大流的思路。考虑建立反向边。每次找费用之和最小的增广路增广。

此时需要求源点到汇点的最短路。由于存在负边权(反向边),需要用到 SPFA。

考虑结合 EK,遂有 EK + SPFA 的求解方式,即将朴素 EK 中的 bfs 替换为 SPFA 即可。

理论时间复杂度上界为 \(O(n^2m^2)\)

点击查看代码
#include <bits/stdc++.h>
#define il inline
#define int long long

using namespace std;

bool Beg;
namespace Zctf1088 {
namespace IO {
	const int bufsz = 1 << 20;
	char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
	#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
	il int read() {
		int x = 0; char ch = getchar(); bool t = 0;
		while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
		while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
		return t ? -x : x;
	}
	char obuf[bufsz], *p3 = obuf, stk[50];
	#define flush() (fwrite(obuf, 1, p3 - obuf, stdout), p3 = obuf)
	#define putchar(ch) (p3 == obuf + bufsz && flush(), *p3++ = (ch))
	il void write(int x, bool t = 0) {
		int top = 0;
		x < 0 ? putchar('-'), x = -x : 0;
		do {stk[++top] = x % 10 | 48; x /= 10;} while(x);
		while (top) putchar(stk[top--]);
		t ? putchar(' ') : putchar('\n');
	}
	struct FL {
		~FL() {flush();}
	} fl;
}
using IO::read; using IO::write;
const int INF = 0x3f3f3f3f3f3f3f3f;
const int N = 1e5, M = 1e6;
int n, m, ss, tt;
struct Edge {
	int nxt, to, w, c;
} edge[M];
int head[N], etot = 1;
il void addEdge(int x, int y, int w, int c) {
	edge[++etot] = {head[x], y, w, c};
	head[x] = etot;
}
il void addEdge1(int x, int y, int w, int c) {
	addEdge(x, y, w, c);
	addEdge(y, x, 0, -c);
}
int dis[N], vis[N], pre[N], mnw[N], S, T;
queue<int> q;
il bool SPFA(int n) {
	for (int i = 1; i <= n; i++) dis[i] = INF, vis[i] = 0;
	while (!q.empty()) q.pop();
	q.push(S);
	dis[S] = 0, mnw[S] = INF, vis[S] = 1;
	while (!q.empty()) {
		int x = q.front(); q.pop();
		vis[x] = 0;
		for (int i = head[x]; i; i = edge[i].nxt) {
			int y = edge[i].to, w = edge[i].w, c = edge[i].c;
			if (w > 0 && dis[x] + c < dis[y]) {
				dis[y] = dis[x] + c;
				mnw[y] = min(mnw[x], w);
				pre[y] = i;
				if (!vis[y]) {
					vis[y] = 1;
					q.push(y);
				}
			}
		}
	}
	return dis[T] < INF;
}
int mf, mc;
il void EK(int n, int s, int t) {
	S = s, T = t;
	while (SPFA(n)) {
		mf += mnw[T];
		mc += mnw[T] * dis[T];
		for (int i, x = t; x != s; x = edge[i ^ 1].to) {
			i = pre[x];
			edge[i].w -= mnw[T];
			edge[i ^ 1].w += mnw[T];
		}
	}
}
signed main() {
	n = read(), m = read(), ss = read(), tt = read();
	for (int i = 1; i <= m; i++) {
		int x = read(), y = read(), w = read(), c = read();
		addEdge1(x, y, w, c);
	}
	EK(n, ss, tt);
	write(mf, 1), write(mc);
	return 0;
}}
bool End;
il void Usd() {cerr << "\nUse: " << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n";}
signed main() {
	Zctf1088::main();
	Usd();
	return 0;
}

类似的,求最大费用最大流则使用求最长路的 SPFA 即可。

上下界网络流

上下界网络流,即对于一个网络 \(G\) 中的一条边 \((u,v)\),其不仅有流量上界 \(c(u,v)\),还有流量的下界 \(b(u,v)\)

无源汇上下界可行流

给定一个有上下界、没有源点汇点的网络 \(G\),求是否存在一个流 \(f\),使得 \(b(u,v) \le f(u,v) \le c(u,v)\)

模板题:无源汇有上下界可行流

不妨设此时每一条边已经流了 \(b(u,v)\) 的流量。

考虑建立新图,新图上每一条边的流量为 \(c(u,v)-b(u,v)\)。此时显然不满足流量守恒,考虑调整。

在新图中加入超级源点 \(S\) 和超级汇点 \(T\)。考虑在新图中通过增减流量来使得流量守恒。对于新图上的一个点,设其净流量为 \(M\)

  • \(M=0\),不做处理。
  • \(M>0\),此时流入大于流出,需要在新图上增加流出。增加流出量就需要增加流入量,考虑从源点向该点连边,容量为 \(M\)。如此,当新图流量平衡的时候,除去这条边,新图上流出就会比流入大 \(M\),与原先的流量差抵消。
  • \(M<0\),此时流入小于流出,类似的,从该点向汇点连边即可,容量为 \(|M|\)

然后跑最大流即可。那么,若 \(S\) 的出边都是满流,则说明原图中存在可行流,否则不存在。

点击查看代码
#include <bits/stdc++.h>
#define il inline
#define int long long

using namespace std;

bool Beg;
namespace Zctf1088 {
namespace IO {
	const int bufsz = 1 << 20;
	char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
	#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
	il int read() {
		int x = 0; char ch = getchar(); bool t = 0;
		while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
		while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
		return t ? -x : x;
	}
	char obuf[bufsz], *p3 = obuf, stk[50];
	#define flush() (fwrite(obuf, 1, p3 - obuf, stdout), p3 = obuf)
	#define putchar(ch) (p3 == obuf + bufsz && flush(), *p3++ = (ch))
	il void write(int x, bool t = 0) {
		int top = 0;
		x < 0 ? putchar('-'), x = -x : 0;
		do {stk[++top] = x % 10 | 48; x /= 10;} while(x);
		while (top) putchar(stk[top--]);
		t ? putchar(' ') : putchar('\n');
	}
	struct FL {
//		~FL() {flush();}
	} fl;
}
using IO::read;
const int INF = 0x3f3f3f3f3f3f3f3f; 
const int maxn = 2e4, N = 1e5, M = 1e6; 
int n, m, l[maxn], r[maxn], f[maxn];
struct Edge {
	int nxt, to, w;
} edge[M];
int head[N], cur[N], etot = 1;
il void addEdge(int x, int y, int w) {
	edge[++etot] = {head[x], y, w};
	head[x] = etot;
}
il void addEdge1(int x, int y, int w) {
	addEdge(x, y, w);
	addEdge(y, x, 0);
}
int dis[N], S, T;
queue<int> q;
il bool bfs(int n) {
	for (int i = 1; i <= n; i++) dis[i] = 0, cur[i] = head[i];
	while (!q.empty()) q.pop();
	dis[S] = 1; q.push(S);
	while (!q.empty()) {
		int x = q.front(); q.pop();
		for (int i = head[x]; i; i = edge[i].nxt) {
			int y = edge[i].to, w = edge[i].w;
			if (w > 0 && !dis[y]) {
				dis[y] = dis[x] + 1;
				if (y == T) return true;
				q.push(y);
			}
		}
	} 
	return false;
}
il int dfs(int x, int flow) {
	if (x == T) return flow;
	int rest = flow;
	for (int i = cur[x]; i && rest; i = edge[i].nxt) {
		cur[x] = i;
		int y = edge[i].to, w = edge[i].w;
		if (w > 0 && dis[y] == dis[x] + 1) {
			int k = dfs(y, min(rest, w));
			if (k == 0) dis[y] = 0;
			rest -= k;
			edge[i].w -= k;
			edge[i ^ 1].w += k;
		}
	}
	return flow - rest;
}
il int dinic(int n, int s, int t) {
	S = s, T = t;
	int res = 0;
	while (bfs(n)) res += dfs(S, INF);
	return res;
}
signed main() {
	n = read(), m = read();
	for (int i = 1; i <= m; i++) {
		int x = read(), y = read(); l[i] = read(), r[i] = read();
		addEdge1(x, y, r[i] - l[i]);
		f[x] -= l[i], f[y] += l[i];
	}
	int sum = 0;
	for (int i = 1; i <= n; i++) {
		if (f[i] > 0) addEdge1(n + 1, i, f[i]);
		if (f[i] < 0) addEdge1(i, n + 2, -f[i]);
		sum += (f[i] > 0) * f[i];
	}
	if (dinic(n + 2, n + 1, n + 2) == sum) {
		printf("YES\n");
		for (int i = 1; i <= m; i++) 
			printf("%lld\n", l[i] + edge[i * 2 + 1].w);
	} else {
		printf("NO\n");
	}
	return 0;
}}
bool End;
il void Usd() {cerr << "\nUse: " << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n";}
signed main() {
	Zctf1088::main();
	Usd();
	return 0;
}

有源汇上下界可行流

给定一个有上下界的网络 \(G\),求是否有一个流 \(f\),满足上下界限制。

考虑沿用无源汇上下界可行流的做法。但是由于 \(s,t\) 的流量不一定守恒,所以考虑从 \(t\)\(s\) 连一条边,下界为 \(0\),上界为 \(\infty\)。那么只要整个图满足流量守恒就一定可以得到一个可行流,且这个可行流的流量为 \(t \rightarrow s\) 这条边的流量。

有源汇上下界最大流

给定一个有上下界的网络 \(G\),求一个流 \(f\),使得其在满足上下界限制的情况下流量最大。

模板题:有源汇有上下界最大流

考虑先跑一遍有源汇上下界可行流,判无解。

考虑有解的情况。关注这个新图上的残量网络,此时超级源点 \(S\) 和超级汇点 \(T\) 所连边应该已经满流,但是原图的源点 \(s\) 和汇点 \(t\) 可能还有剩余。于是,考虑删去 \(t \rightarrow s\) 的边后再在残量网络上以 \(s,t\) 为源汇点跑一遍最大流,再加上可行流流量即为答案。

点击查看代码
#include <bits/stdc++.h>
#define il inline
#define int long long

using namespace std;

bool Beg;
namespace Zctf1088 {
namespace IO {
	const int bufsz = 1 << 20;
	char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
	#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
	il int read() {
		int x = 0; char ch = getchar(); bool t = 0;
		while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
		while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
		return t ? -x : x;
	}
	char obuf[bufsz], *p3 = obuf, stk[50];
	#define flush() (fwrite(obuf, 1, p3 - obuf, stdout), p3 = obuf)
	#define putchar(ch) (p3 == obuf + bufsz && flush(), *p3++ = (ch))
	il void write(int x, bool t = 0) {
		int top = 0;
		x < 0 ? putchar('-'), x = -x : 0;
		do {stk[++top] = x % 10 | 48; x /= 10;} while(x);
		while (top) putchar(stk[top--]);
		t ? putchar(' ') : putchar('\n');
	}
	struct FL {
//		~FL() {flush();}
	} fl;
}
using IO::read;
const int INF = 0x3f3f3f3f3f3f3f3f; 
const int maxn = 2e4, N = 1e5, M = 1e6; 
int n, m, ss, tt, l[maxn], r[maxn], f[maxn];
struct Edge {
	int nxt, to, w;
} edge[M];
int head[N], cur[N], etot = 1;
il void addEdge(int x, int y, int w) {
	edge[++etot] = {head[x], y, w};
	head[x] = etot;
}
il void addEdge1(int x, int y, int w) {
	addEdge(x, y, w);
	addEdge(y, x, 0);
}
int dis[N], S, T;
queue<int> q;
il bool bfs(int n) {
	for (int i = 1; i <= n; i++) dis[i] = 0, cur[i] = head[i];
	while (!q.empty()) q.pop();
	dis[S] = 1; q.push(S);
	while (!q.empty()) {
		int x = q.front(); q.pop();
		for (int i = head[x]; i; i = edge[i].nxt) {
			int y = edge[i].to, w = edge[i].w;
			if (w > 0 && !dis[y]) {
				dis[y] = dis[x] + 1;
				if (y == T) return true;
				q.push(y);
			}
		}
	} 
	return false;
}
il int dfs(int x, int flow) {
	if (x == T) return flow;
	int rest = flow;
	for (int i = cur[x]; i && rest; i = edge[i].nxt) {
		cur[x] = i;
		int y = edge[i].to, w = edge[i].w;
		if (w > 0 && dis[y] == dis[x] + 1) {
			int k = dfs(y, min(rest, w));
			if (k == 0) dis[y] = 0;
			rest -= k;
			edge[i].w -= k;
			edge[i ^ 1].w += k;
		}
	}
	return flow - rest;
}
il int dinic(int n, int s, int t) {
	S = s, T = t;
	int res = 0;
	while (bfs(n)) res += dfs(S, INF);
	return res;
}
signed main() {
	n = read(), m = read(), ss = read(), tt = read();
	for (int i = 1; i <= m; i++) {
		int x = read(), y = read(); l[i] = read(), r[i] = read();
		addEdge1(x, y, r[i] - l[i]);
		f[x] -= l[i], f[y] += l[i];
	}
	addEdge1(tt, ss, INF);
	int sum = 0;
	for (int i = 1; i <= n; i++) {
		if (f[i] > 0) addEdge1(n + 1, i, f[i]);
		if (f[i] < 0) addEdge1(i, n + 2, -f[i]);
		sum += (f[i] > 0) * f[i];
	}
	int res = dinic(n + 2, n + 1, n + 2);
	if (res != sum) return printf("please go home to sleep\n"), 0;
	res = edge[(m + 1) * 2 + 1].w;
	edge[(m + 1) * 2].w = edge[(m + 1) * 2 + 1].w = 0;
	res += dinic(n, ss, tt);
	printf("%lld\n", res);
	return 0;
}}
bool End;
il void Usd() {cerr << "\nUse: " << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n";}
signed main() {
	Zctf1088::main();
	Usd();
	return 0;
}

有源汇上下界最小流

给定一个有上下界的网络 \(G\),求一个流 \(f\),使得其在满足上下界限制的情况下流量最小。

模板题:有源汇有上下界最小流

和最大流类似的,先跑一遍可行流判无解。不同的,最大流是在残量网络中尽可能地榨干剩余流量,最小流反之,将流量退回去。考虑反向边。在残量网络中从 \(t\)\(s\) 跑一遍最大流即可。再用可行流减去这个最大流即为答案。

点击查看代码
#include <bits/stdc++.h>
#define il inline
#define int long long

using namespace std;

bool Beg;
namespace Zctf1088 {
namespace IO {
	const int bufsz = 1 << 20;
	char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
	#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
	il int read() {
		int x = 0; char ch = getchar(); bool t = 0;
		while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
		while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
		return t ? -x : x;
	}
	char obuf[bufsz], *p3 = obuf, stk[50];
	#define flush() (fwrite(obuf, 1, p3 - obuf, stdout), p3 = obuf)
	#define putchar(ch) (p3 == obuf + bufsz && flush(), *p3++ = (ch))
	il void write(int x, bool t = 0) {
		int top = 0;
		x < 0 ? putchar('-'), x = -x : 0;
		do {stk[++top] = x % 10 | 48; x /= 10;} while(x);
		while (top) putchar(stk[top--]);
		t ? putchar(' ') : putchar('\n');
	}
	struct FL {
//		~FL() {flush();}
	} fl;
}
using IO::read;
const int INF = 0x3f3f3f3f3f3f3f3f; 
const int maxn = 2e5, N = 1e5, M = 1e6; 
int n, m, ss, tt, l[maxn], r[maxn], f[maxn];
struct Edge {
	int nxt, to, w;
} edge[M];
int head[N], cur[N], etot = 1;
il void addEdge(int x, int y, int w) {
	edge[++etot] = {head[x], y, w};
	head[x] = etot;
}
il void addEdge1(int x, int y, int w) {
	addEdge(x, y, w);
	addEdge(y, x, 0);
}
int dis[N], S, T;
queue<int> q;
il bool bfs(int n) {
	for (int i = 1; i <= n; i++) dis[i] = 0, cur[i] = head[i];
	while (!q.empty()) q.pop();
	dis[S] = 1; q.push(S);
	while (!q.empty()) {
		int x = q.front(); q.pop();
		for (int i = head[x]; i; i = edge[i].nxt) {
			int y = edge[i].to, w = edge[i].w;
			if (w > 0 && !dis[y]) {
				dis[y] = dis[x] + 1;
				if (y == T) return true;
				q.push(y);
			}
		}
	} 
	return false;
}
il int dfs(int x, int flow) {
	if (x == T) return flow;
	int rest = flow;
	for (int i = cur[x]; i && rest; i = edge[i].nxt) {
		cur[x] = i;
		int y = edge[i].to, w = edge[i].w;
		if (w > 0 && dis[y] == dis[x] + 1) {
			int k = dfs(y, min(rest, w));
			if (k == 0) dis[y] = 0;
			rest -= k;
			edge[i].w -= k;
			edge[i ^ 1].w += k;
		}
	}
	return flow - rest;
}
il int dinic(int n, int s, int t) {
	S = s, T = t;
	int res = 0;
	while (bfs(n)) res += dfs(S, INF);
	return res;
}
signed main() {
	n = read(), m = read(), ss = read(), tt = read();
	for (int i = 1; i <= m; i++) {
		int x = read(), y = read(); l[i] = read(), r[i] = read();
		addEdge1(x, y, r[i] - l[i]);
		f[x] -= l[i], f[y] += l[i];
	}
	addEdge1(tt, ss, INF);
	int sum = 0;
	for (int i = 1; i <= n; i++) {
		if (f[i] > 0) addEdge1(n + 1, i, f[i]);
		if (f[i] < 0) addEdge1(i, n + 2, -f[i]);
		sum += (f[i] > 0) * f[i];
	}
	int res = dinic(n + 2, n + 1, n + 2);
	if (res != sum) return printf("please go home to sleep\n"), 0;
	res = edge[(m + 1) * 2 + 1].w;
	edge[(m + 1) * 2].w = edge[(m + 1) * 2 + 1].w = 0;
	res -= dinic(n, tt, ss);
	printf("%lld\n", res);
	return 0;
}}
bool End;
il void Usd() {cerr << "\nUse: " << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n";}
signed main() {
	Zctf1088::main();
	Usd();
	return 0;
}

有源汇上下界最小费用可行流

给定一个有上下界、有费用的网络 \(G\),求一个流 \(f\),使得在满足上下界的条件的情况下费用最小(最大同理)。

考虑用类似有源汇上下界可行流的方式解决。对于 \((u,v)\) 的边,容量为 \(c(u,v)-b(u,v)\),费用为 \(w(u,v)\),其余附加边的费用均为 \(0\)

若存在可行流,则新图上跑出来的流一定为新图上最大流,于是在新图上跑最小费用最大流即求出新图上的最小费用,再加上跑满下界需要的费用,即为原图的最小费用。

有源汇上下界最小费用最大流

给定一个有上下界、有费用的网络 \(G\),求一个流 \(f\),使得在满足上下界的情况下且流最大的情况下费用最小(费用最大、流最小均同理)。

依照有源汇上下界最大流的做法,在求出最小费用可行流后,在残量网络上从 \(s\)\(t\) 跑一遍最小费用最大流即可,然后费用相加即为答案。

posted @ 2026-01-10 18:29  Zctf1088  阅读(55)  评论(0)    收藏  举报