• Codeforces 888G


    888G - Xor-MST

    题意

    给出一个 (n) 个点的无向完全图,结点 (i) 有权值 (a_i)。两个结点 (i, j) 所连边的权值为 (a_i xor a_j)
    求最小生成树边的权值之和。

    分析

    好题!
    首先这里有 Trie 的一个经典利用,在一些数中找到一个数与某个数异或起来最大(小)。

    然后就很神奇了。

    构造最小生成树,一般要优先选择权值最小的边(Kruskal算法),假如我们有左右两边各一些结点,每一边都是一颗树了,现在想要合并成完整一棵树,只需要找到一条权值最小的边,其两个结点分别在左右两边即可。
    利用这个想法,我们就可以分治递归去做这道题。

    但是有一个问题,怎么保证左右两边的点就一定能分别构造出满足最优性的树呢?
    答案是排序。左边的最多只会有一条边连向右边的结点,否则不会最优(异或之后那些二进制位会变为1,我们想让变成 1 的二进制位尽可能少,变为 0 的尽可能多),所以我们尽可能想让小异或小、大异或大。

    然后就是要切割区间了,从高到低去遍历二进制位,找到一个最小的数且对应的二进制位为1,记位置为 mid 。
    递归 [l, mid) [mid, r)。

    假如递归完后,我们要从左边找一条连向右边结点的权值最小的边,为了节省时间,我们不能去更新 Trie 树,用 mx[now] 维护转化成二进制数后(从根结点到 now 结点构成的)前缀中的最大的那个数的下标,注意到所有数字是排好序后再插入到 Trie 树中的,如果到某一结点 now ,当前二进制位为 c ,nxt[now][c] != 0,此时有 mx[nxt[now][c]] >= mid ,说明有右边的结点且前缀相同,否则一旦前缀不同,就要加上那个二进制位对应的数,然后向下继续走,这样就能保证在 Trie 树中只考虑 [mid, r) 中的结点。

    讲半天,还是代码好懂,建议模拟一下简单数据。

    5
    2 3 4 7 8
    

    code

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int MAXN = 2e5 + 10;
    int n, a[MAXN];
    int L, nxt[MAXN * 30][2], mx[MAXN * 30];
    void insert(int x, int id) {
        int now = 0;
        for(int i = 29; i >= 0; i--) {
            int c = (x >> i) & 1;
            if(!nxt[now][c]) nxt[now][c] = L++;
            now = nxt[now][c];
            mx[now] = max(mx[now], id);
        }
    }
    int query(int x, int id) {
        int now = 0, res = 0;
        for(int i = 29; i >= 0; i--) {
            int c = (x >> i) & 1;
            if(nxt[now][c] && mx[nxt[now][c]] >= id) now = nxt[now][c];
            else {
                now = nxt[now][!c];
                res += (1 << i);
            }
        }
        return res;
    }
    ll ans;
    void solve(int l, int r, int k) { // [l, r)
        if(l == r || k < 0) return;
        int mid = l;
        while(mid < r && !((a[mid] >> k) & 1)) mid++;
        solve(l, mid, k - 1);
        solve(mid, r, k - 1);
        if(mid == r || mid == l) return;
        int res = 2e9;
        for(int i = l; i < mid; i++) {
            res = min(res, query(a[i], mid));
        }
        ans += res;
    }
    int main() {
        L = 1;
        cin >> n;
        for(int i = 0; i < n; i++) cin >> a[i];
        sort(a, a + n);
        for(int i = 0; i < n; i++) insert(a[i], i);
        solve(0, n, 29);
        cout << ans << endl;
        return 0;
    }
    
  • 相关阅读:
    linux修改主机名
    selinux详解及配置文件
    linux磁盘分区
    识别光纤,模块
    free命令详解
    Linux系统修改网卡名(eth0-3)
    CentOS7的systemctl使用
    ELK集群
    ELK故障:elk在运行一段时间后,没有数据。
    superviosrd进程管理
  • 原文地址:https://www.cnblogs.com/ftae/p/7823087.html
Copyright © 2020-2023  润新知