Kruskal 重构树学习笔记

Kruskal 重构树学习笔记

前置知识

最小生成树的 Kruskal 算法、最近公共祖先(LCA)。

重构树

在 Kruskal 算法执行过程中(不妨设求最小生成树),我们会按边权升序依次加入若干条边。

首先初始化并查集,共 \(n\) 个集合,第 \(i\) 个集合初始包含且仅包含第 \(i\) 个点,维护当前的连通块情况。每一次加边到最小生成树中时,我们都合并了目前的两个连通块,也就是两个集合。我们要建立重构树,在每次合并操作时新建一个点,点权为加入的这条边的边权,将合并的两个集合的根节点作为这个新建点的左右儿子,并将两个集合以及新建点合并,以新建点为集合的根(代表元)。

例如,对于下面这个无向图:

它的最小生成树的 Kruskal 重构树如下:(不知道画图的时候怎么了,虚拟节点的编号都大了 \(1\),但懒得重画了,将就看吧)

重构树性质

不妨设求最小生成树,Kruskal 重构树有如下性质:

  1. 重构树是一棵恰有 \(n\) 个叶子节点的完满二叉树,每个非叶子节点都恰有 \(2\) 个儿子,重构树的点数为 \(2n-1\)
  2. 重构树的点权符合大根堆的性质。
  3. 原图中两点间所有简单路径的最大边权最小值,等于最小生成树上两点之间边权最大值,等于重构树上两点 LCA 的点权。
  4. 到点 \(u\) 的简单路径上最大边权最小值 \(\le k\) 的所有节点 \(v\) 均在重构树上的某棵子树内,且恰为该子树内的所有叶子节点。

例题

P1967 [NOIP2013 提高组] 货车运输

本题可以用最大生成树+倍增做,但是这里讲一下 Kruskal 重构树的做法。

求最大生成树的重构树,所求即为重构树上 \(x,y\) 两点的 LCA 的点权。

代码
//By: OIer rui_er
#include <bits/stdc++.h>
#define rep(x,y,z) for(int x=(y);x<=(z);x++)
#define per(x,y,z) for(int x=(y);x>=(z);x--)
#define debug(format...) fprintf(stderr, format)
#define fileIO(s) do{freopen(s".in","r",stdin);freopen(s".out","w",stdout);}while(false)
using namespace std;
typedef long long ll;
const int N = 1e5+5;

int n, m, q, now, dis[N], fa[N][20], val[N];
template<typename T> void chkmin(T& x, T y) {if(x > y) x = y;}
template<typename T> void chkmax(T& x, T y) {if(x < y) x = y;}
struct Edge {
	int u, v, w;
	Edge(int x=0, int y=0, int z=0) : u(x), v(y), w(z) {}
}G[N];
vector<int> e[N];
struct Dsu {
	int fa[N];
	void init(int x) {rep(i, 1, x) fa[i] = i;}
	int find(int x) {return x == fa[x] ? x : fa[x] = find(fa[x]);}
	bool merge(int x, int y) {
		int u = find(x), v = find(y);
		if(u == v) return 0;
		fa[u] = v;
		return 1;
	}
}dsu;
void kruskal() {
	dsu.init(2*n-1);
	sort(G+1, G+1+m, [](const Edge& x, const Edge& y) {
		return x.w > y.w;
	});
	now = n;
	rep(i, 1, m) {
		ll u = dsu.find(G[i].u), v = dsu.find(G[i].v), w = G[i].w;
		if(u != v) {
			val[++now] = w;
			dsu.merge(u, now);
			dsu.merge(v, now);
			e[now].push_back(u);
			e[now].push_back(v);
		}
	}
}
void dfs(int u, int f) {
	dis[u] = dis[f] + 1;
	fa[u][0] = f;
	rep(i, 1, 19) fa[u][i] = fa[fa[u][i-1]][i-1];
	for(int v : e[u]) dfs(v, u);
}
int LCA(int u, int v) {
	if(dis[u] < dis[v]) swap(u, v);
	per(i, 19, 0) if(dis[fa[u][i]] >= dis[v]) u = fa[u][i];
	if(u == v) return u;
	per(i, 19, 0) if(fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
	return fa[u][0];
}

int main() {
	scanf("%d%d", &n, &m);
	rep(i, 1, m) {
		int u, v, w;
		scanf("%d%d%d", &u, &v, &w);
		G[i] = Edge(u, v, w);
	}
	kruskal();
	per(i, now, 1) if(!dis[i]) dfs(i, 0);
	for(scanf("%d", &q);q;q--) {
		int s, t;
		scanf("%d%d", &s, &t);
		int lca = LCA(s, t);
		if(!lca) puts("-1");
		else printf("%d\n", val[lca]);
	}
	return 0;
}

CF1706E Qpwoeirut and Vertices

以边的读入顺序为边权,求最小生成树的重构树,所求即为重构树上 \(l\sim r\) 点的 LCA 点权。

有性质:区间 LCA 等于区间内 dfs 序最小点和最大点的 LCA。

使用 ST 表维护 dfs 序最小值和最大值然后求解即可。

需要特判 \(l=r\)

代码
//By: OIer rui_er
#include <bits/stdc++.h>
#define rep(x,y,z) for(int x=(y);x<=(z);x++)
#define per(x,y,z) for(int x=(y);x>=(z);x--)
#define debug(format...) fprintf(stderr, format)
#define fileIO(s) do{freopen(s".in","r",stdin);freopen(s".out","w",stdout);}while(false)
using namespace std;
typedef long long ll;
const int N = 5e5+5;

int T, n, m, q, now, dfn[N], tms, dis[N], id[N], fa[N][20], val[N];
template<typename T> void chkmin(T& x, T y) {if(x > y) x = y;}
template<typename T> void chkmax(T& x, T y) {if(x < y) x = y;}
struct Edge {
	int u, v, w;
	Edge(int x=0, int y=0, int z=0) : u(x), v(y), w(z) {}
}G[N];
vector<int> e[N];
struct Dsu {
	int fa[N];
	void init(int x) {rep(i, 1, x) fa[i] = i;}
	int find(int x) {return x == fa[x] ? x : fa[x] = find(fa[x]);}
	bool merge(int x, int y) {
		int u = find(x), v = find(y);
		if(u == v) return 0;
		fa[u] = v;
		return 1;
	}
}dsu;
void kruskal() {
	dsu.init(2*n-1);
	sort(G+1, G+1+m, [](const Edge& x, const Edge& y) {
		return x.w < y.w;
	});
	now = n;
	rep(i, 1, m) {
		ll u = dsu.find(G[i].u), v = dsu.find(G[i].v), w = G[i].w;
		if(u != v) {
			val[++now] = w;
			dsu.merge(u, now);
			dsu.merge(v, now);
			e[now].push_back(u);
			e[now].push_back(v);
		}
	}
}
void dfs(int u, int f) {
	dfn[u] = ++tms;
	dis[u] = dis[f] + 1;
	fa[u][0] = f;
	rep(i, 1, 19) fa[u][i] = fa[fa[u][i-1]][i-1];
	for(int v : e[u]) dfs(v, u);
}
int LCA(int u, int v) {
	if(dis[u] < dis[v]) swap(u, v);
	per(i, 19, 0) if(dis[fa[u][i]] >= dis[v]) u = fa[u][i];
	if(u == v) return u;
	per(i, 19, 0) if(fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
	return fa[u][0];
}
struct SparseTable {
	int lg[N], mx[N][20], mn[N][20];
	void init(int* a, int n) {
		lg[0] = -1;
		rep(i, 1, n) lg[i] = lg[i>>1] + 1;
		rep(i, 1, n) mx[i][0] = mn[i][0] = a[i];
		rep(j, 1, 19) {
			rep(i, 1, n-(1<<j)+1) {
				mx[i][j] = max(mx[i][j-1], mx[i+(1<<(j-1))][j-1]);
				mn[i][j] = min(mn[i][j-1], mn[i+(1<<(j-1))][j-1]);
			}
		}
	}
	int qmax(int l, int r) {
		int k = lg[r-l+1];
		return max(mx[l][k], mx[r-(1<<k)+1][k]);
	}
	int qmin(int l, int r) {
		int k = lg[r-l+1];
		return min(mn[l][k], mn[r-(1<<k)+1][k]);
	}
}st;

int main() {
	for(scanf("%d", &T);T;T--) {
		scanf("%d%d%d", &n, &m, &q);
		rep(i, 1, m) {
			int u, v;
			scanf("%d%d", &u, &v);
			G[i] = Edge(u, v, i);
		}
		kruskal();
		dfs(now, 0);
		st.init(dfn, now);
		rep(i, 1, now) id[dfn[i]] = i;
		while(q--) {
			int l, r;
			scanf("%d%d", &l, &r);
			int u = id[st.qmax(l, r)], v = id[st.qmin(l, r)];
			if(u == v) printf("%d%c", 0, " \n"[!q]);
			else printf("%d%c", val[LCA(u, v)], " \n"[!q]);
		}
		tms = 0;
		rep(i, 1, now) e[i].clear();
	}
	return 0;
}

P4768 [NOI2018] 归程

转化题意,即从节点 \(v\) 出发,先在海拔大于 \(p\) 的边上乘车走一段,然后走最短路径回到节点 \(1\)

首先通过 Dijkstra 算法预处理每个节点到节点 \(1\) 的最短路径,然后对海拔求最大生成树的重构树。

由上面提到的性质三和四,即求到节点 \(v\) 最小海拔最大值大于 \(p\) 的所有节点中,到节点 \(1\) 最近的那个点的最短路径长度。

在重构树上 DP 预处理每棵子树内的叶子节点中,到节点 \(1\) 最短路径长度的最小值。对于每次询问,先从 \(v\) 开始在重构树上倍增,找到性质四所说的那棵子树,然后子树的 DP 值即为答案。

关于 SPFA:它死了。

代码
//By: OIer rui_er
#include <bits/stdc++.h>
#define rep(x,y,z) for(ll x=(y);x<=(z);x++)
#define per(x,y,z) for(ll x=(y);x>=(z);x--)
#define debug(format...) fprintf(stderr, format)
#define fileIO(s) do{freopen(s".in","r",stdin);freopen(s".out","w",stdout);}while(false)
using namespace std;
typedef long long ll;
const ll N = 4e5+5;

ll T, n, m, q, k, s, dis[N], vis[N], val[N], fa[N][20], lst;
template<typename T> void chkmin(T& x, T y) {if(x > y) x = y;}
template<typename T> void chkmax(T& x, T y) {if(x < y) x = y;}
struct Edge1 {
	ll u, v, a;
	Edge1(ll x=0, ll y=0, ll z=0) : u(x), v(y), a(z) {}
}G[N];
struct Edge2 {
	ll v, l;
	Edge2(ll x=0, ll y=0) : v(x), l(y) {}
};
vector<Edge2> e[N];
vector<ll> et[N];
void dijkstra(ll s) {
	memset(dis, 0x3f, sizeof(dis));
	memset(vis, 0, sizeof(vis));
	priority_queue<tuple<ll, ll> > q;
	dis[s] = 0;
	q.emplace(0, s);
	while(!q.empty()) {
		ll u = get<1>(q.top()); q.pop();
		if(vis[u]) continue;
		vis[u] = 1;
		for(Edge2 i : e[u]) {
			ll v = i.v, l = i.l;
			if(dis[v] > dis[u] + l) {
				dis[v] = dis[u] + l;
				q.emplace(-dis[v], v);
			}
		}
	}
}
struct Dsu {
	ll fa[N];
	void init(ll x) {rep(i, 1, x) fa[i] = i;}
	ll find(ll x) {return x == fa[x] ? x : fa[x] = find(fa[x]);}
	bool merge(ll x, ll y) {
		ll u = find(x), v = find(y);
		if(u == v) return 0;
		fa[u] = v;
		return 1;
	}
}dsu;
void kruskal() {
	dsu.init(2*n-1);
	sort(G+1, G+1+m, [](const Edge1& x, const Edge1& y) {
		return x.a > y.a;
	});
	ll now = n;
	rep(i, 1, m) {
		ll u = dsu.find(G[i].u), v = dsu.find(G[i].v), a = G[i].a;
		if(u != v) {
			val[++now] = a;
			dsu.merge(u, now);
			dsu.merge(v, now);
			et[now].push_back(u);
			et[now].push_back(v);
		}
	}
}
void dfs(ll u, ll f) {
	fa[u][0] = f;
	rep(i, 1, 19) fa[u][i] = fa[fa[u][i-1]][i-1];
	for(ll v : et[u]) {
		dfs(v, u);
		chkmin(dis[u], dis[v]);
	}
}

int main() {
	for(scanf("%lld", &T);T;T--) {
		scanf("%lld%lld", &n, &m);
		rep(i, 1, m) {
			ll u, v, l, a;
			scanf("%lld%lld%lld%lld", &u, &v, &l, &a);
			G[i] = Edge1(u, v, a);
			e[u].emplace_back(v, l);
			e[v].emplace_back(u, l);
		}
		dijkstra(1);
		kruskal();
		dfs(2*n-1, 0);
		for(scanf("%lld%lld%lld", &q, &k, &s);q;q--) {
			ll v, p;
			scanf("%lld%lld", &v, &p);
			v = (v + k * lst - 1) % n + 1;
			p = (p + k * lst) % (s + 1);
			per(i, 19, 0) if(val[fa[v][i]] > p) v = fa[v][i];
			printf("%lld\n", lst=dis[v]);
		}
		lst = 0;
		rep(i, 1, m) G[i] = Edge1();
		memset(val, 0, sizeof(val));
		memset(fa, 0, sizeof(fa));
		rep(i, 1, n) e[i].clear();
		rep(i, 1, 2*n-1) et[i].clear();
	}
	return 0;
}

P6765 [APIO2020] 交换城市

我还没有做。

posted @ 2022-09-05 23:42  rui_er  阅读(694)  评论(0编辑  收藏  举报