最小生成树 Boruvka 算法

昨天学习了 Boruvka 算法求最小生成树。

它有什么用呢?

有时候给出的图的边数可能达到 O(n^2) 级别,这时无法使用传统的 Prim 或 Kruskal 求最小生成树,这时就要用到 Boruvka 算法。

Boruvka 算法的流程如下:

最开始,共有 n 个联通块,分别是每个结点。

每次对于每个联通块,寻找联通块内的点与联通块外的点最小的边,记录下来,找完所有最小的边后统一合并。不断进行这样的操作,直到联通块个数为 1。

为什么这样做是正确的呢?它其实是每次进行多步的 Kruskal,或者说多路增广的 Kruskal,我们发现这两者的操作本质上是相同的,都是每次找出最小的边,只不过 Boruvka 将其中一些边的合并提前进行了,因为对于一个联通块来说,总有一条边要让这个联通块和另外一个联通块合并,根据 Kruskal 算法,与其他联通块内的点相连最小的边一定是要算进去的,因此将其提前合并依旧是正确的。

为什么时间复杂度是正确的?因为每次每个联通块都会与另外一个联通块合并,所以每次联通块个数都至少缩减为原来的 \frac{1}{2},因此只会有 O(\log n) 次合并,若每次合并总时间复杂度是接近线性的,那么总时间复杂度就是 \Theta(n\log n) 的。

这里有道例题。

CF1550F Jumping Around

题意:

给出长度为 n 的序列 a,定义两点 i,j 间边权为 |a_i-b_j|,开始时在点 s,边权在 [d-k,d+k] 中的两点可以互相到达,d 是常数,每次给出 i 和 k,询问对于 k,能否从点 s 到达点 i

稍微把边权转化一下,发现 ||a_i-a_j|-d|\le k 的点联通,那么令新的边权为 ||a_i-a_j|-d|,对于一条路径,其最大边权不大于 k。于是,若是 s 到 i 所有路径中最小的最大边权不大于 k,就可以从 s 到达 i

最小的最大边权,显然求这个东西需要最小瓶颈生成树,或者 Kruskal 重构树。实际上前者在本题是可以实现的,因为 s 是固定的,询问离线下来不断从 s 往外扩展就好了,估计还很好写。那还学 Boruvka 干什么

考虑使用 Boruvka 算法,对于每次求一个联通块内的点与其他点最小边权操作,可以将联通块内的点从存着所有点的 set 中删掉,然后对于联通块内每个点 p,分别二分出与之相连边权最小的点,也就是 |a_p-a_i| 与 d 最接近的点 i,这样的点可能有四个(左边两个右边两个),取最小的那个即可。合并时用启发式合并来合并联通块的点,这样做扫完每个联通块的时间复杂度是 O(n \log n) 的,另外启发式合并的总时间复杂度是 O(n\log n) 的,因此总时间复杂度是 O(n \log^2n)

最后,由于 s 是常量,所以直接以 s 为根节点 dfs 一遍即可求出边权最大值,并不需要树剖。

Code:

#include <bits/stdc++.h>
#define ll long long
#define lp(i, j, n) for(int i = j; i <= n; ++i)
#define dlp(i, n, j) for(int i = n; i >= j; --i)
#define mst(n, v) memset(n, v, sizeof(n))
#define mcy(n, v) memcpy(n, v, sizeof(v))
#define INF 1e18
#define MAX4 0x3f3f3f3f
#define MAX8 0x3f3f3f3f3f3f3f3f
#define mkp(a, b) make_pair(a, b)
#define pii pair<int, int>
#define pll pair<ll, ll>
#define co(x) cerr << (x) << ' '
#define cod(x) cerr << (x) << endl
#define fi first
#define se second
#define eps 1e-8
#define pb(x) emplace_back(x)
 
using namespace std;
 
const int N = 200010, M = 1000010;
 
int n, Q, s, D, ans[N], fa[N];
int a[N], no[M], dis[N];
pii to[N];
vector<int> pt[N], e[N];
set<int> pt0, rt;
 
int mab(int x) { return x < 0 ? -x : x; }
 
int getv(int i, int j) { return mab(mab(a[i] - a[j]) - D); }
 
vector<int> cl;
void mer(int x, int y) {
    // co(x), cod(y);
    e[x].pb(y), e[y].pb(x);
    int fx = fa[x], fy = fa[y];
    if(pt[fx].size() > pt[fy].size()) swap(x, y), swap(fx, fy);
    cl.emplace_back(fx);
    for(auto p : pt[fx]) pt[fy].emplace_back(p), fa[p] = fy;
}
 
void solve() {
    lp(i, 1, n) rt.insert(i), fa[i] = i, pt[i].pb(i), pt0.insert(a[i]);
    while(rt.size() > 1) {
        cod(rt.size());
        for(auto r : rt) {
            for(auto p : pt[r]) pt0.erase(a[p]);
            int res = 1e9;
            for(auto p : pt[r]) {
                auto upd = [&] (int idx) { if(getv(idx, p) < res) res = getv(idx, p), to[r] = { p, idx }; };
                auto it = pt0.lower_bound(a[p] + D);
                if(it != pt0.end()) upd(no[*it]);
                if(it != pt0.begin()) --it, upd(no[*it]);
                it = pt0.lower_bound(a[p] - D);
                if(it != pt0.end()) upd(no[*it]);
                if(it != pt0.begin()) --it, upd(no[*it]);
            }
            for(auto p : pt[r]) pt0.insert(a[p]);
        }
        for(auto r : rt) {
            if(fa[to[r].fi] != fa[to[r].se]) mer(to[r].fi, to[r].se);
        }
        for(auto p : cl) rt.erase(p);
        cl.clear();
    }
}
 
void dfs(int now, int fa) {
    for(auto v : e[now]) {
        if(v == fa) continue;
        dis[v] = max(dis[now], getv(now, v));
        dfs(v, now);
    }
}
 
signed main() {
    //freopen(".in", "r", stdin);
    //freopen(".out", "w", stdout);
#ifndef READ
    ios::sync_with_stdio(false);
    cin.tie(0);
#endif
    cin >> n >> Q >> s >> D;
    lp(i, 1, n) cin >> a[i], no[a[i]] = i;
    int k, idx;
    solve();
    dfs(s, 0);
    lp(i, 1, Q) {
        cin >> idx >> k;
        if(dis[idx] <= k) cout << "Yes" << endl;
        else cout << "No" << endl;
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值