最小割树:Gomory-Hu Tree

正式开始之前考虑一个问题:给定一张无向图,q次询问任意两点之间的最小割,如何实现?

最小割树

对于上面哪个问题最显而易见的做法是每次都跑一遍 dinic,时间复杂度 \(O(qn^2m)\),显然不行。
考虑到每次询问涉及到两个点之间的最小割,所以可以考虑建一棵树,利用树上两点之间的路径是唯一的这个性质来储存两个节点之间的最小割,因而就有了最小割树这么个东西。
最小割树的定义:

树边 (u,v) 的边权是 u,v 的最小割,删掉 (u,v) 之后树被分成的两部分是一个最小割方案中点被分成的两部分,且任意两点 S,T 之间的最小割等于 S,T 在最小割树路径上的最小值。

严格的最小割树是很难构建的,况且大多数时候我们呢并不需要知道具体的最小割方案,所以我们一般构建的都是“等价流树”,即只满足“且任意两点 S,T 之间的最小割等于 S,T 在最小割树路径上的最小值”的树。不少人都认为最小割树等价于等价流树,但实际上这是不严谨的。(具体可以参照2016年集训队论文集)。

为了方便,这里规定:\(\lambda(x,y)\) 表示 x,y 之间的最小割。 \(w(x,y)\) 表示两点之间的边权。

算法详解

基本性质

树上两点之间的路径上边权的最小值即为这两点之间的最小割,证明需要介绍两条引理。
引理1:对于任意三点,\(\lambda(a,b) \geq min\{\lambda(a,c),\lambda(c,b)\}\)

proof: 根据最小割定义,引理显然成立。

引理2:假设去掉 \(\lambda(a,b)\) 之后得到两部分点集,一部分与 a 相连记作\(A\),另一部分与 b 相连记作 \(B\),\(\forall p \in A, q \in B, \lambda(a,b) \geq \lambda(p, q)\)

proof:反证法,假设 \(\lambda(a,b) < \lambda(p, q)\),那么我们割掉所有的 a,b 之间的边之后 p,q 仍然联通,所以 a -> p -> q -> b 得出 a,b 仍然联通,与原假设矛盾,引理得证。

根据以上引理,对于两点 x,y,假设 (s,t) 为 x 到 y 路径上边权最小的边,根据定理1有 \(\lambda(x,y) \geq \lambda(s,t)\),根据引理2有 $ \lambda(x,y) \leq \lambda(s,t)$。所以 $ \lambda(x,y) = \lambda(s,t)$

算法实现

我们用分治建树,每次在正在处理的点的区间中任选两个点 a,b,求出他们之间的最小割\(\lambda(a,b)\),在等价流树上对这两点进行连边,边权为 \(\lambda(a,b)\),在 dinic 最后一次 bfs 时就求出的 dis 数组中的权值分为非零和零两部分,也就是说,这个区间去掉最小割后被分成了两部分,递归处理即可。

代码细节

1.推荐使用namespace避免命名冲突
2.求最小边权的时候可以用树上倍增等多种方式实现
3.最开始的图要连双向边(因为你不知道流是去往哪个方向的)

实现

题目是 P4897 【模板】最小割树(Gomory-Hu Tree)
代码:

点击查看代码
#include <bits/stdc++.h>

#define ll long long
#define dub long double
const int inf = 1e9 + 7;
const int MAXN = 5100 + 10;

using namespace std;
namespace mxf{
	struct edge{int u, v, f, nxt;} e[MAXN << 2];
	int head[MAXN], cnt = 1;
	int s, t, ans;
	int cur[MAXN], dis[MAXN];
	queue < int > q;
	void add( int u, int v, int w ){
		e[++cnt] = (edge){ u, v, w, head[u] };
		head[u] = cnt;
		e[++cnt] = (edge){ v, u, 0, head[v] };
		head[v] = cnt;
	}
	bool bfs(){
		memset(dis, 0, sizeof(dis));
		memcpy(cur, head, sizeof(head));
		dis[s] = 1;
		q.push(s);
		while(!q.empty( )){
			int x = q.front( ); q.pop( );
			for(int i = head[x]; i; i = e[i].nxt){
				int y = e[i].v;
				if(!e[i].f or dis[y]) continue;
				dis[y] = dis[x] + 1;
				q.push(y);
			}
		}
		return dis[t];
	}
	int dfs(int x, int low){
		if(x == t){
			ans += low;
			return low;
		}
		int used = 0, rlow = 0;
		for(int &i = cur[x]; i; i = e[i].nxt){
			int y = e[i].v;
			if(e[i].f and dis[y] == dis[x] + 1){
				if((rlow = dfs(y, min(e[i].f, low - used)))){
					e[i].f -= rlow;
					e[i^1].f += rlow;
					used += rlow;
					if(used == low) break;
				}
			}
		}
		return used;
	}
	void pushback(){
		for(int i = 2; i <= cnt; i += 2)
			e[i].f += e[i^1].f, e[i^1].f = 0;
		return;
	}
	int dinic( ){
		pushback( );
		ans = 0;
		while( bfs( ) ) dfs( s, inf );
		return ans;
	}
}

struct edge{int u, v, w, nxt;} e[MAXN << 2];
int head[MAXN], cnt;
int tmp[MAXN], node[MAXN];
int dep[MAXN];
int fa[510][15], mi[510][15];
int n, m;

void add(int u, int v, int w){
	e[++cnt] = (edge){u, v, w, head[u]};
	head[u] = cnt;
	e[++cnt] = (edge){v, u, w, head[v]};
	head[v] = cnt;
}

inline int read( ){
    int x = 0 ; short w = 0 ; char ch = 0;
    while( !isdigit(ch) ) { w|=ch=='-';ch=getchar();}
    while( isdigit(ch) ) {x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
    return w ? -x : x;
}

void work(int l, int r){
	if(l >= r) return;
	int cnt1 = l, cnt2 = r;
	int x = node[l], y = node[l + 1];
	mxf :: s = x; mxf :: t = y;
	int cut = mxf :: dinic( );
	add(x, y, cut);
	for(int i = l; i <= r; i++)
		if(mxf :: dis[node[i]]) tmp[cnt1++] = node[i];
		else tmp[cnt2--] = node[i];
	for(int i = l; i <= r; i++)
		node[i] = tmp[i];
	work(l, cnt1 - 1);
	work(cnt2 + 1, r);
}

void dfs(int x, int f){
	dep[x] = dep[f] + 1;
	fa[x][0] = f;
	for(int i = 1; i <= 8; i++){
		fa[x][i] = fa[fa[x][i-1]][i-1];
		mi[x][i] = min(mi[x][i-1], mi[fa[x][i-1]][i-1]);
	}
	for(int i = head[x]; i; i = e[i].nxt){
		int y = e[i].v;
		if(y == f) continue;
		mi[y][0] = e[i].w;
		dfs(y, x);
	}
}

int getcut(int x, int y){
	int res = inf;
	if(dep[x] < dep[y]) swap(x, y);
	for(int i = 8; i >= 0; i--)
		if(dep[fa[x][i]] >= dep[y]){
			res = min(res, mi[x][i]);
			x = fa[x][i];
		}
	if(x == y) return res;
	for(int i = 8; i >= 0; i--){
		if(fa[x][i] != fa[y][i]){
			res = min(res, min(mi[x][i], mi[y][i]));
			x = fa[x][i]; y = fa[y][i];
		}
	}
	res = min(res, min(mi[x][0], mi[y][0]));
	return res;
}

signed main( ){
	
	n = read( ); m = read( );
	
	for(int i = 1; i <= m; i++){
		int u = read( ), v = read( ), w = read( );
		mxf :: add(u, v, w);
		mxf :: add(v, u, w);
	}
	
	for(int i = 0; i <= n; i++)
		node[i] = i;
	
	work(0, n);
	dfs(1, 0);
	
	int q = read( );
	
	for(int i = 1; i <= q; i++){
		int x = read( ), y = read( );
		printf("%lld\n",getcut(x, y));
	}
	
	return 0;
}
posted @ 2023-03-29 15:55  Kun_9  阅读(269)  评论(0编辑  收藏  举报