题意:
给你一个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;
}
后记:
奇怪的是,上次碰到的启发式合并题没用启发式直接合并可以过,这次居然也可以过。
是数据水了,还是复杂度计算问题?