CF 888G - Xor-MST 分治 贪心 字典树 启发式合并

题意:

给你一个n个数,每两个数之间边的权值是a[i] ^ a[j]。

问最小生成树大小。

题解:

最小生成树可以用kruskal思想。(其实应该是Boruvka算法,这里记录一下)

每次选择最小的边。

我们建立一颗01字典树,那么很明显对于一颗子树内的点,他肯定也是连这个子树内的点最好,然后每个子树就会构成一个联通块,去连一条最小的边到兄弟子树,如此递归。

相当于每次把两颗子树合并,用启发式合并即可。

复杂度O(nlogn * 30)。

代码:

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <bitset>
#include <map>
#include <vector>
#include <stack>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <cmath>
#ifdef LOCAL
#define debug(x) cout<<#x<<" = "<<(x)<<endl;
#else
#define debug(x) 1;
#endif

#define chmax(x,y) x=max(x,y)
#define chmin(x,y) x=min(x,y)
#define lson id<<1,l,mid
#define rson id<<1|1,mid+1,r
#define lowbit(x) x&-x
#define mp make_pair
#define pb push_back
#define fir first
#define sec second
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll, int> pii;

const ll MOD = 1e9 + 7;
const double eps = 1e-10;
const int INF = 0x3f3f3f3f;
const ll INFLL = 0x3f3f3f3f3f3f3f3f;
const int MAXN = 2e5 + 5;

struct nodes {
    int nxt[2], num, val;
} tree[MAXN * 40];
int tot;

void add (int x) {
    int now = 0;
    for (int i = 30; i >= 0; i--) {
        int id = x >> i & 1;
        if (!tree[now].nxt[id]) {
            tree[now].nxt[id] = ++tot;
        }
        now = tree[now].nxt[id];
        tree[now].num++;
    }
    tree[now].val = x;
}

int query(int x, int st, int dep) {
    int now = st;
    for(int i = dep; i >= 0; i--) {
        int id = x >> i & 1;
        if(tree[tree[now].nxt[id]].num)
        now = tree[now].nxt[id];
        else now = tree[now].nxt[!id];
    }
    return tree[now].val;
}

ll ans;

vector<int> v[MAXN * 33];
int tran[MAXN * 33];

void solve (int id, int dep) {
    tran[id] = id;
    if (dep == -1) {
        v[id].pb (tree[id].val);
        return ;
    }
    int nxt0 = tree[id].nxt[0], nxt1 = tree[id].nxt[1];
    if(tree[nxt0].num) solve (nxt0, dep - 1);
    if(tree[nxt1].num) solve (nxt1, dep - 1);
    int minn = INF;
    if (v[tran[nxt0]].size() < v[tran[nxt1]].size() ) {
        for (int j : v[tran[nxt0]]) {
            minn = min(minn, j ^ query(j, nxt1, dep - 1));
            v[tran[nxt1]].pb(j);
        }
        tran[id] = tran[nxt1];
        v[tran[nxt0]].clear();
    } else {
        for (int j : v[tran[nxt1]]) {
            if(j == 4) debug(dep)
            minn = min(minn, j ^ query(j, nxt0, dep - 1));
            v[tran[nxt0]].pb(j);
        }
        tran[id] = tran[nxt0];
        v[tran[nxt1]].clear();
    }

    ans += minn == INF ? 0 : minn;
}

int a[MAXN];

int main() {
#ifdef LOCAL
    freopen ("input.txt", "r", stdin);
#endif
    int n;
    scanf ("%d", &n);
    for (int i = 1; i <= n; i++) scanf ("%d", &a[i]), add (a[i]);
    solve(0, 30);
    printf("%lld\n", ans);
    return 0;
}

后记:

奇怪的是,上次碰到的启发式合并题没用启发式直接合并可以过,这次居然也可以过。

是数据水了,还是复杂度计算问题?

猜你喜欢

转载自blog.csdn.net/c6376315qqso/article/details/82749412