题目
思路
主要思路都写在了代码的注释里
为什么要按照异或值给a数组排序呢?
方便记录经过字典树某个节点的最大和最小值
Boruvka算法是什么呢?
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define inf (1 << 30)
#define rep(i, s, t) for(int i = s; i <= t; ++ i)
#define maxn 200005
int n, m, a[maxn], L[maxn * 32], R[maxn * 32], ch[2][maxn * 32], rt, cnt;
void insert(int&k, int id, int dep) {
if(!k) k = ++ cnt;
// L数组和R数组是什么意思呢?
// L表示经过k节点的最小值,R表示最大值,因为之前排序了
if(!L[k]) L[k] = id; R[k] = id;
if(dep == -1) return;
// ch? id表示当前树的节点编号,表示a[id]的dep位是0还是1,走字典树的左儿子还是右儿子
// ch记录的是字典树某个节点的编号
insert(ch[(a[id] >> dep) & 1][k], id, dep - 1);
}
// 这个函数在找到异或的最小值
int query(int k, int x, int dep) {
if(dep == -1) return 0;
int v = (x >> dep) & 1;
// 如果这个节点存在,尽量使得高位为0
if(ch[v][k]) return query(ch[v][k], x, dep - 1);
// 不存在就不得不将异或值置为1
return query(ch[v ^ 1][k], x, dep - 1) + (1 << dep);
}
// dfs要解决什么问题呢?执行Boruvka算法
// 以k为根节点的字典树,这个字典树合并所有节点后,得到的最小生成树
// 从根节点到叶子节点的路径就代表了一个a[i]
// 树越深,2个叶子节点合并的异或值就越小,采用boruvka算法
int dfs(int k, int dep) {
if(dep == -1) return 0;
// 左右儿子都存在
if(ch[0][k] && ch[1][k]) {
int ans = inf;
// 遍历0子树所有的a[i]
rep(i, L[ch[0][k]], R[ch[0][k]]) {
// 在1子树中查异或最小值,2树合并
ans = min(ans, query(ch[1][k], a[i], dep - 1) + (1 << dep));
}
// 0树处理子问题的代价+1树处理子问题的代价+ans
return dfs(ch[0][k], dep - 1) + dfs(ch[1][k], dep - 1) + ans;
}
else if(ch[0][k]) return dfs(ch[0][k], dep - 1);
else if(ch[1][k]) return dfs(ch[1][k], dep - 1);
return 0;
}
signed main() {
scanf("%lld", &n);
rep(i, 1, n) scanf("%lld", &a[i]);
sort(a + 1, a + n + 1); //?
// 这里为什么是30呢? 因为int就是有30位
// rt? 表示当前节点,一开始rt为0,后面rt为1
rep(i, 1, n) insert(rt, i, 30);
printf("%lld", dfs(rt, 30));
return 0;
}