主要是借用了最小生成树的Boruvka算法的思想:
在两个连通块内找到一条最短的路径,连接两个连通块合并成一个连通块
那些年我用异或最小生成树做过的题:
板子
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
int n, m, k;
namespace XorMST {
int a[N]; // 原数组
int trie[N * 30][2]; // 字典树
int tot;//编号
// 将x以二进制的形式插入到01字典树里
void Insert(int x) {
int rt = 0;
for (int i = 29; i >= 0; i--) {
int now = (x >> i) & 1; // 拆分x的每一位
if (!trie[rt][now])
trie[rt][now] = ++tot;
rt = trie[rt][now];
}
}
// 在字典树上找x能异或出来的最小值
int Search(int x) {
int ans = 0, rt = 0;
for (int i = 29; i >= 0; i--) {
int now = (x >> i) & 1;
if (trie[rt][now]) {
rt = trie[rt][now];
} else {
rt = trie[rt][now ^ 1];
ans |= (1 << i);
}
}
return ans;
}
// 对a[l...r]进行分治
// dep 当前二进制搜索到的位置
ll ans = 0;
void dfs(int l, int r, int dep) {
if (dep == -1 || l >= r) return;
int mid = l - 1;
// 以二进制第dep位置上的数字划分为两个集合 左边0 右边1
while (mid < r && ((a[mid + 1] >> dep) & 1) == 0) mid++;
// 先将左右两个集合内部连通 具体操作就是分治的内容 假设我们已经连通好了
// 然后就是 按照算法的思想 在两个连通块内找到一条最小边 将两者连通 形成一个大的连通块
// 每次贪最短路 显然是可以贪心的得到最小生成树
dfs(l, mid, dep - 1);
dfs(mid + 1, r, dep - 1);
if (mid == l - 1 || mid == r) return;
//将左边集合里的值 插入到01字典树中
for (int i = l; i <= mid; i++) {
Insert(a[i]);
}
// 找最小边
// 根据题意描述 边的权值和边的两个端点有关 E(i,j)=a[i]^a[j]
// 显然是在右边集合里取一个值 去左边集合里找异或最小值
int tmp = INT_MAX;
for (int i = mid + 1; i <= r; i++) {
tmp = min(tmp, Search(a[i]));
}
//统计答案
ans += tmp;
//清空字典树
for (int i = 0; i <= tot; i++) {
trie[i][0] = trie[i][1] = 0;
}
tot = 0;
}
}
using namespace XorMST;
void solve_cf888g() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
sort(a + 1, a + 1 + n);//必须要排序
dfs(1, n, 29);
cout << XorMST::ans << endl;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
solve_cf888g();
return 0;
}