题意:
给定一个有 n 个点的完全图,两点边权为点权异或结果,求最小生成树。(n <= 2e5)
链接:
https://vjudge.net/problem/CodeForces-888G
解题思路:
由于是完全图,不能将边权都求出来,无法使用 Kruskal 和 Prim。这里结合另一种最小生成树算法的思想,Boruvka算法,每轮选择连接联通块的最小边,由于是异或,即对一个联通块建立字典树,另一个联通块内的点求其最小异或和。考虑贪心,根据 bit 位分组,先根据最高位为 0、1 分两组,之间选择一条最小边连接,而两边是子问题,递归求解即可。
参考代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define pb push_back
#define sz(a) ((int)a.size())
#define mem(a, b) memset(a, b, sizeof a)
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
const int maxn = 2e5 + 5;
const int maxm = 6e6 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
int nxt[maxm][2];
int a[maxn];
int n, cnt;
int add(){
mem(nxt[++cnt], 0); return cnt;
}
void init(){
cnt = -1; add();
}
void insert(int x){
int p = 0;
for(int i = 29; i >= 0; --i){
int t = (x >> i) & 1;
if(!nxt[p][t]) nxt[p][t] = add();
p = nxt[p][t];
}
}
int query(int x){
int p = 0, ret = 0;
for(int i = 29; i >= 0; --i){
int t = (x >> i) & 1;
if(nxt[p][t]) p = nxt[p][t];
else p = nxt[p][t ^ 1], ret |= 1 << i;
}
return ret;
}
ll dfs(int l, int r, int d){
if(d < 0 || l >= r) return 0;
int mid = 0;
for(int i = l; i <= r; ++i){
if((a[i] >> d) & 1) break;
mid = i;
}
if(!mid || mid == r) return dfs(l, r, d - 1);
int tmp = 1 << 30; init();
for(int i = l; i <= mid; ++i) insert(a[i]);
for(int i = mid + 1; i <= r; ++i) tmp = min(tmp, query(a[i]));
return tmp + dfs(l, mid, d - 1) + dfs(mid + 1, r, d - 1);
}
int main(){
// ios::sync_with_stdio(0); cin.tie(0);
cout << inf << endl;
cout << (1 << 30) << endl;
scanf("%d", &n);
for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
sort(a + 1, a + 1 + n);
ll ret = dfs(1, n, 29);
printf("%lld\n", ret);
return 0;
}