克鲁斯卡尔重构树
title: 克鲁斯卡尔重构树
categories:
- ICPC
tags:
- null
abbrlink: 1877ddc4
date: 2023-09-02 00:00:00
一类以并查集在建树过程中维护各种信息的值——克鲁斯卡尔重构树前身
第一次见到是在zzu的校赛中,印象深刻。
H. Sum of Maximum Weights
题意
给定一棵树,求树上任意两点间最短路径中的最大边权的和。
官方 Solution
- 将边按权值排序,每次处理当前的最大权值。
- 处理每条边时,由于树的性质,边的起点和终点一定连通。设两个组 $U$ 和 $V$,则 $U$ 组中任意成员到 $V$ 组中任意成员的最大路径边权必为当前边的权值 $e$,故可以写出 $ans += siz[u] \times siz[v] \times e$。
- 将两组合并,重复上述过程,最终得到答案。
我的理解
如果了解克鲁斯卡尔重构树,这就是一道板子题。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e6 + 5;
const int M = N * 2;
int a[N], b[N];
int f[N], siz[N];
int Get(int u) {
return u == f[u] ? u : f[u] = Get(f[u]);
}
struct edge {
int u, v, e;
} e[N];
bool cmp(edge x, edge y) {
return x.e < y.e;
}
signed main() {
int n;
cin >> n;
for (int i = 1; i < n; i++) {
scanf("%lld %lld %lld", &e[i].u, &e[i].v, &e[i].e);
}
for (int i = 1; i <= n; i++) {
f[i] = i;
siz[i] = 1;
}
sort(e + 1, e + n, cmp);
int ans = 0;
for (int i = 1; i < n; i++) {
edge t = e[i];
int u = t.u, v = t.v, e = t.e;
u = Get(u), v = Get(v);
if (siz[u] < siz[v]) swap(u, v);
ans += siz[u] * siz[v] * e;
f[u] = v;
siz[v] += siz[u];
}
cout << ans << endl;
}
牛客小白月赛25C
题意
树上有黑白点,可以将一个黑点改成白点,求修改后最大白色连通块。
Solution
- 对于白色连通块,用并查集维护连通性和
size
。 - 暴力枚举黑色节点,累加其相邻的白色连通块大小,取最大值即为答案。
- Debug:注意字符串下标和数组下标混用的情况。特判全是白色节点的情况。
int n, m;
int a[N];
int col[N];
struct DSU {
vector<int> f, siz;
DSU() {}
DSU(int n) {
init(n);
}
void init(int n) {
f.resize(n);
std::iota(f.begin(), f.end(), 0);
siz.assign(n, 1);
}
int find(int x) {
while (x != f[x]) {
x = f[x] = f[f[x]];
}
return x;
}
bool same(int x, int y) {
return find(x) == find(y);
}
bool merge(int x, int y) {
x = find(x);
y = find(y);
if (x == y) return false;
siz[x] += siz[y];
f[y] = x;
return true;
}
int size(int x) {
return siz[find(x)];
}
};
vector<int> e[N];
void solve() {
cin >> n;
string s;
cin >> s;
for (int i = 0; i < s.size(); i++) {
if (s[i] == 'W') col[i + 1] = 1;
else col[i + 1] = 0;
}
DSU dsu(n + 1);
for (int i = 1; i <= n - 1; i++) {
int u, v;
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
if (col[u] && col[v]) dsu.merge(u, v);
}
int ans = 0;
for (int i = 1; i <= n; i++) {
int sum = 0;
if (col[i] == 0) {
for (auto v : e[i]) {
if (col[v] == 1) sum += dsu.siz[dsu.find(v)];
}
ans = max(ans, sum + 1);
}
}
if (ans == 0) ans = n;
cout << ans << endl;
}
2020牛客寒假算法基础集训营1 F
题意
给定一棵树,每个顶点被染成白色或黑色。求取两个不同的点,它们的简单路径上有且仅有一个黑色点的取法有多少。
Solution
- 经过一个黑点的路径有两种:
- 两个端点都是白点。
- 其中一个端点是黑点。
- 预处理每个白点连通块上的白点个数。
- 设某黑点连接了 $k$ 个白点,第 $i$ 个白点的权值为 $f(i)$。
- 第一种路径的数量为 $\sum_{i=1}^k \sum_{j=i+1}^k f(i) \times f(j)$,可以用前缀和优化。
- 第二种路径的数量为 $\sum_{i=1}^k f(i)$。
克鲁斯卡尔重构树板子题
题意
给出一张普通无向图,多次查询。每次查询给出两个点,求连通两个点的所有路径中,每条路径都有最小边权值,求这些最小边权值中的最大值。
Solution
- 先跑最大生成树,树上路径最小值即为答案。
- 使用克鲁斯卡尔重构树,每次合并时开一个新点,新点的权值为当前边的权值。
- 查询时,找到两个点的 LCA,LCA 的权值即为答案。
int n, m;
int a[N];
vector<int> e[N];
int fa[N], son[N], dep[N], siz[N];
int top[N];
void dfs1(int u, int f) {
fa[u] = f;
siz[u] = 1;
dep[u] = dep[f] + 1;
for (int v : e[u]) {
if (v == f) continue;
dfs1(v, u);
siz[u] += siz[v];
if (siz[son[u]] < siz[v]) son[u] = v;
}
}
void dfs2(int u, int t) {
top[u] = t;
if (!son[u]) return;
dfs2(son[u], t);
for (int v : e[u]) {
if (v == fa[u] || v == son[u]) continue;
dfs2(v, v);
}
}
int lca(int u, int v) {
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]]) swap(u, v);
u = fa[top[u]];
}
return dep[u] < dep[v] ? u : v;
}
void add(int u, int v) {
e[u].push_back(v);
}
struct edges {
int u, v, w;
bool operator<(const edges &tmp) {
return w > tmp.w;
}
};
edges edge[M];
void solve() {
int q;
cin >> n >> m;
DSU dsu(2 * n + 1);
for (int i = 1; i <= m; i++) {
int u, v, w;
cin >> u >> v >> w;
edge[i] = {u, v, w};
}
sort(edge + 1, edge + 1 + m);
int cnt = n;
for (int i = 1; i <= m; i++) {
auto [u, v, w] = edge[i];
u = dsu.find(u), v = dsu.find(v);
if (u != v) {
cnt++;
dsu.merge(cnt, u);
dsu.merge(cnt, v);
add(cnt, u);
add(u, cnt);
add(cnt, v);
add(v, cnt);
a[cnt] = w;
}
}
for (int i = 1; i <= 2 * n; i++) {
if (dsu.find(i) == i) {
dfs1(i, 0);
dfs2(i, i);
}
}
cin >> q;
for (int i = 1; i <= q; i++) {
int u, v;
cin >> u >> v;
if (dsu.find(u) != dsu.find(v)) {
cout << -1 << endl;
continue;
}
int mid = lca(u, v);
cout << a[mid] << endl;
}
}
相关题目
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 爱飞鱼的blog!