[学习笔记]最小割树 (Gomory-Hu Tree)

最小割树又称 Gomory-Hu 树, 由 Ralph Edward Gomory 和 Te Chiang Hu 于 1961 年发表的论文中共同提出。最小割树可以在 \(n − 1\) 次最大流中构建, 并通过树上 RMQ 求任意两点之间的最小割。

板子题:

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

1.构造

定义 \(cut(u, v)\) 为无向图中 \(u, v\) 之间的最小割大小。

最小割树最开始初始化为 \(n\) 个不相连的点。先任意选取两个节点 \(u, v\), 求出它们的最小割 \(cut(u, v)\), 跑最大流时, 满流的边就对应最小割中割掉的边。通过这组割, 可以把图划分成两个部分, \(u\) 所在的点集记作 \(S\), \(v\) 所在的点集记作 \(T\)。在最小割树上连接 \(u, v\), 边权为 \(cut(u, v)\)。点集 \(S, T\) 分别构成 \(u, v\) 树上的子树, 递归处理 \(S, T\), 最终得到的树形结构就是就是最小割树。

2.性质

最小割树满足一个很强的性质:两点之间的最小割 等于 在最小割树上两点间路径中边的最小权。

下面是简单证明。

  • 定理1 任选 \(u, v\) 跑一次最小割, 将图分成了 \(S, T\) 两个集合。
    \((x\in S, y\in T)\Longrightarrow cut(x, y)\leq cut(u, v)\)
    证明 : 割断 \(u, v\) 的割显然也分隔了 \(S, T\) , 所以 \(cut(u, v)\) 是上界。

  • 定理2 任取三点 \(a, b, c\)\(cut(a, b), cut(a, c), cut(b, c)\) 中最小值至少出现两次。
    证明 : 不妨假设 \(cut(a, b)\) 是最小的, 先割开, 再讨论 \(c\)\(S, T\) 的那个集合内。
    不妨设其在 \(S\) 内, 由 {定理1} 得到 \(cut(b, c)\leq cut(a, b)\)
    而由 \(cut(a, b)\) 最小的假设则得到 \(cut(b, c)=cut(a, b)\) , 出现两次。

  • 推论1 : \(cut(a, b)\geq \min\{cut(a, c), cut(b, c)\}\)
    证明 : 若 \(cut(a, b)\) 不是最小值, 显然成立, 若 \(cut(a, b)\) 是最小值, 则一定会出现两次, 也会出现在 \(\min\) 中。

  • 推论2 对于两点 \((u, v)\) , 有 \(cut(u, v)\geq\min\{cut(u, w_1), cut(w_1, w_2)...cut(w_m, v)\}\)
    证明 : 首先由 {定理2} 展开 \(cut(u, v)\), 得到 \(cut(u, v)\geq\min\{cut(u, w_1), cut(w_1, v)\}\)
    我们重复上述操作展开 \(cut(w_1, v)\) 可以得到 \(cut(w_1, v)\geq\min\{cut(w_1, w_2), cut(w_2, v)\}\),
    合并两个 \(\min\) 集得到 \(cut(u, v)\geq\min\{cut(u, w1), cut(w_1, w_2), cut(w_2, v)\}\)
    每次展开最后一项, 重复多次即可得到 {推论2}。

  • 定理3(最小割树定理) 如上所述。
    \(u, v\) 为欲求的两个点, \(u, v\) 在最小割树的路径构成一个边集 \((w_1, w_2), (w_2, w_3)...(w_{m-1}, w_m)\), 其中 \(w_1=u, w_m=v\)
    证明 : 首先, 不难得到 \(u, v\)\(w_i, w_{i+1}\) 最小割的两侧。由 {定理1} , \(cut(u, v)\leq cut(w_i, w_{i+1})\)
    然后根据 {定理2.2} 又能得到 \(cut(u, v)\geq\min\{cut(w_1, w_2), cut(w_2, w_3)...cut(w_{m-1}, w_m)\}\)
    也就是 \(\min\{cut(w_i, w_{i+1})\}\leq cut(u, v)\leq\min\{cut(w_i, w_{i+1})\}\),
    这就得到了 \(cut(u, v)=\min\{cut(w_i, w_{i+1}\}\)

代码(去掉了最大流的实现):

// by xm
// 2024.8.1
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

const ll N = 500 + 5, M = 1500 + 5, Inf = 1e18;

int T, n, m, s, t;

struct Edge {
	ll to, vol, next;
} G[M * 2], G_copy[M * 2];

ll Dinic() { /*...*/ }
ll fa[N], dep[N], cut[N];
void Gomory_Hu(vector<int>& v_set) { // v_set:待处理的点集
	if(v_set.size() <= 1) return; // 空集或单点都不用处理
	
	// 随机选两个点
	s = v_set[0], t = v_set[1];
	
	// 用原图跑 Dinic, G_copy 是原图的拷贝
	for(int i = 2; i <= e_cnt; i++) G[i] = G_copy[i];
	ll maxFlow = Dinic();
	
	// 建最小割树
	fa[t] = s, dep[t] = dep[s] + 1, cut[t] = maxFlow;
	
	// 最小割将 V_set 分为 S_set, T_set 两个点集
	vector<int> S_set, T_set;
	for(auto v : v_set) {
		if(dis[v] != Inf) S_set.push_back(v);
		else T_set.push_back(v);
	}
	
	// 递归处理 s, t 子树 S_set, T_set
	Gomory_Hu(S_set);
	Gomory_Hu(T_set);
}
ll solve(int x, int y) { // 暴力跳链, 懒得写倍增了
	ll res = Inf;
	while(x != y) { // 每次只跳深度大的点, 最后一定会相遇在 LCA(x, y)
		if(dep[y] > dep[x]) swap(x, y);
		res = min(res, cut[x]);
		x = fa[x];
	}
	return res;
}

int main() {
	ios::sync_with_stdio(false), cin.tie(nullptr);
	
	cin >> n >> m;
	for(ll i = 1, u, v, w; i <= m; i++) {
		cin >> u >> v >> w;
		add(u, v, w);
		add(v, u, w);
	}
	vector<int> v_set;
	for(int i = 0; i <= n; i++) v_set.push_back(i);
	Gomory_Hu(v_set);
	
	cin >> q;	
	for(int i = 1, u, v; i <= q; i++) {
		cin >> u >> v;
		cout << solve(u, v) << "\n";
	}
	
	return 0;
}
posted @ 2024-08-01 16:38  EnochYoung  阅读(224)  评论(1)    收藏  举报