Boruvka 算法
创建时间:2026-04-22
Boruvka 算法的思想
Boruvka 算法是一种善于处理稠密的最小生成树算法,在一般图中时间复杂度 \(O(m \log n)\),略优于 Kruskal 的 \(O(m \log m)\)。在有特殊性质的完全图最小生成树问题中,Boruvka 算法可以在不建图的情况下求出最小生成树,通常可以做到 \(O(n \cdot \mathrm{polylog}(n))\)。
在一般图 MST 问题中,我们先将图的 \(n\) 个点视为 \(n\) 个连通块并进行迭代。每次迭代找出每个连通块最小的出边,处理完所有连通块后,合并每个连通块与那条出边对应的连通块。不难发现,最多迭代 \(O(\log n)\) 次,每次迭代的复杂度为 \(O(m)\),总复杂度 \(O(m \log n)\)。
虽然一般图 MST 中还是 Kruskal 更好写,但在一些完全图问题 MST 问题中,Boruvka 的迭代合并的思想就尤为重要了。
Boruvka 算法的应用
这是一般图 MST 的板题,套用前面的思路即可。
code
#include <bits/stdc++.h>
using namespace std;
const int N = 5005, M = 2e5 + 50, inf = 1e9;
int n, m, f[N], ans;
int find(int x) {
return f[x] == x ? x : f[x] = find(f[x]);
}
struct Edge {
int u, v, w;
} es[M];
pair<int, int> et[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= m; i++) {
cin >> es[i].u >> es[i].v >> es[i].w;
}
iota(f + 1, f + n + 1, 1);
for (bool flag = true; flag; ) {
for (int i = 1; i <= n; i++) {
et[i] = make_pair(inf, 0);
}
for (int i = 1; i <= m; i++) {
int u = find(es[i].u);
int v = find(es[i].v);
if (u == v) {
continue;
}
et[u] = min(et[u], make_pair(es[i].w, v));
et[v] = min(et[v], make_pair(es[i].w, u));
}
flag = false;
for (int i = 1; i <= n; i++) {
if (et[i].second == 0) {
continue;
}
flag = true;
int u = find(i), v = find(et[i].second);
if (u != v) {
ans += et[i].first;
f[u] = v;
}
}
}
for (int i = 2; i <= n; i++) {
if (find(i) != find(1)) {
cout << "orz\n";
return 0;
}
}
cout << ans << '\n';
return 0;
}
有 \(n\) 块石头,位置为 \(a_1, a_2, \cdots, a_n\)。\(q\) 次询问,从 \(a_s\) 出发,每次跳到距离在 \(d - k\) 到 \(d + k\) 之间的石头,其中 \(s, d\) 是常量,问能不能跳到 \(i\)。
把石头两两连边,\((i, j)\) 的边权为 \(||a_i - a_j| - d|\),建出最小生成树,则每次询问即 \(s\) 到 \(i\) 的最大边权是否 \(\le k\)。
直接建图显然爆了。故我们用 Boruvka 求解它的 MST,大体逻辑不变,只需要修改每次迭代找最小出边的部分。
如果是用 set 寻找最小出边,时间复杂度为 \(O(n \log^2 n + q)\);如果每次迭代暴力预处理距离每个点最近的两个连通块不同的石头,则可以做到 \(O(n \log n + q)\)。
code(set 版)
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 50, V = 1e6 + 50;
int n, q, s, d, a[N], id[V], f[N], ans[N];
vector<int> vs[N];
set<int> pos;
struct Edge {
int u, v, w;
bool operator < (const Edge& rhs) const {
return w < rhs.w;
}
};
Edge et[N];
Edge get(int u, int v) {
return Edge{u, v, abs(abs(a[u] - a[v]) - d)};
}
vector<Edge> g[N];
void dfs(int u, int fa, int maxw) {
ans[u] = maxw;
for (auto edg : g[u]) {
int v = edg.v, w = edg.w;
if (v != fa) {
dfs(v, u, max(maxw, w));
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> q >> s >> d;
for (int i = 1; i <= n; i++) {
cin >> a[i];
id[a[i]] = i;
pos.insert(a[i]);
f[i] = i;
vs[i].push_back(i);
}
while (count(f + 1, f + n + 1, f[1]) < n) {
for (int i = 1; i <= n; i++) {
et[i] = Edge{0, 0, V};
if (f[i] != i) {
continue;
}
for (int u : vs[i]) {
pos.erase(a[u]);
}
for (int u : vs[i]) {
auto it1 = pos.lower_bound(a[u] - d);
auto it2 = pos.lower_bound(a[u] + d);
if (it1 != pos.begin()) {
et[i] = min(et[i], get(u, id[*prev(it1)]));
}
if (it1 != pos.end()) {
et[i] = min(et[i], get(u, id[*it1]));
}
if (it2 != pos.begin()) {
et[i] = min(et[i], get(u, id[*prev(it2)]));
}
if (it2 != pos.end()) {
et[i] = min(et[i], get(u, id[*it2]));
}
}
for (int u : vs[i]) {
pos.insert(a[u]);
}
}
for (int i = 1; i <= n; i++) {
if (et[i].w == V) {
continue;
}
int u = et[i].u, v = et[i].v, w = et[i].w;
int x = f[u], y = f[v];
if (x == y) {
continue;
}
g[u].push_back(Edge{u, v, w});
g[v].push_back(Edge{v, u, w});
if (vs[x].size() > vs[y].size()) {
for (int z : vs[y]) {
f[z] = x;
vs[x].push_back(z);
}
} else {
for (int z : vs[x]) {
f[z] = y;
vs[y].push_back(z);
}
}
}
}
dfs(s, 0, 0);
for (int i, k; q--; ) {
cin >> i >> k;
cout << (ans[i] <= k ? "Yes\n" : "No\n");
}
return 0;
}
P11307 [COTS 2016] 建造费 Pristojba:
给出一个 \(n\) 个点的简单无向图,初始时没有边,有 \(m\) 次操作,对于所有 \(v \in [l, r]\),连边 \((u, v)\),边权为 \(a_u + a_v\),求这个图的 MST 的边权和。
依旧使用 Boruvka 迭代,每轮迭代对每个点 \(u\) 寻找最小的不属于同一个连通块的出边。形式化的说,每次迭代需要对每个 \(u\) 求出 \(a_v\) 最小的 \(v\) 满足 \(u, v\) 有边且不属于同一个连通块。
把每条无向边视为两条有向边:\(u \rightarrow v, v \rightarrow u\)(\(v \in [l, r]\))。对 \(u \rightarrow v\) 的边,使用线段树维护区间内最大的两个连通块不同的 \(a\) 即可;对 \(v \rightarrow u\) 的边,把 \(u\) 挂到线段树上打 tag,可以使用标记永久化优化常数,且更好写。
code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 50, inf = 1e9;
int n, m, p[N], f[N];
int find(int x) {
return f[x] == x ? x : f[x] = find(f[x]);
}
struct Tuple {
int x, l, r;
} tp[N];
struct Edge {
int v, w;
Edge() : v(0), w(inf) {}
Edge(int v, int w) : v(v), w(w) {}
friend bool operator < (const Edge& a, const Edge& b) {
return a.w < b.w;
}
friend Edge operator + (const Edge& a, int b) {
return Edge(a.v, a.w + b);
}
} e[N], et[N];
struct Data {
Edge mn, se;
Data(Edge mn = Edge(), Edge se = Edge()) : mn(mn), se(se) {}
Edge get(int u) const {
return find(mn.v) == find(u) ? se : mn;
}
friend Data operator + (const Data& a, const Data& b) {
if (a.mn < b.mn) {
return Data(a.mn, min(a.se, b.get(a.mn.v)));
} else {
return Data(b.mn, min(b.se, a.get(b.mn.v)));
}
}
} dat[N << 2], tag[N << 2];
#define ls cur << 1
#define rs cur << 1 | 1
void build(int cur, int l, int r) {
tag[cur] = Data();
if (l == r) {
dat[cur] = Edge(l, p[l]);
return ;
}
int mid = l + r >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
dat[cur] = dat[ls] + dat[rs];
}
void push(int cur, int l, int r) {
if (l == r) {
e[l] = min(e[l], tag[cur].get(l));
return ;
}
tag[ls] = tag[ls] + tag[cur];
tag[rs] = tag[rs] + tag[cur];
int mid = l + r >> 1;
push(ls, l, mid);
push(rs, mid + 1, r);
}
void link(int cur, int l, int r, int L, int R, int u) {
if (L <= l && r <= R) {
e[u] = min(e[u], dat[cur].get(u));
tag[cur] = tag[cur] + Edge(u, p[u]);
return ;
}
int mid = l + r >> 1;
if (L <= mid) {
link(ls, l, mid, L, R, u);
}
if (mid + 1 <= R) {
link(rs, mid + 1, r, L, R, u);
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> p[i];
}
for (int i = 1; i <= m; i++) {
cin >> tp[i].x >> tp[i].l >> tp[i].r;
}
iota(f + 1, f + n + 1, 1);
long long ans = 0;
for (int num = 0; num < n - 1; ) {
for (int i = 1; i <= n; i++) {
e[i] = et[i] = Edge();
}
build(1, 1, n);
for (int i = 1; i <= m; i++) {
link(1, 1, n, tp[i].l, tp[i].r, tp[i].x);
}
push(1, 1, n);
for (int i = 1; i <= n; i++) {
et[find(i)] = min(et[find(i)], e[i] + p[i]);
}
for (int i = 1; i <= n; i++) {
if (et[i].v == 0) {
continue;
}
int u = find(i), v = find(et[i].v);
if (u != v) {
num++;
ans += et[i].w;
f[u] = v;
}
}
}
cout << ans << '\n';
return 0;
}

浙公网安备 33010602011771号