CF888G Xor-MST
大意
给出
n
n
n个数
a
[
1...
n
]
a[1...n]
a[1...n]
求
n
n
n个点的完全图最小生成树
i
,
j
i,j
i,j的边权为
a
[
i
]
x
o
r
a
[
j
]
a[i] \ \ \ xor \ \ a[j]
a[i] xor a[j]
题解
前置芝士:Boruvka求最小生成树
因为kruskal和边数有关系,所以会GG
这题可以用Boruvka + trie解决
首先是把每个数按照二进制拆分插到trie上
可以先排序再插,这样可以保证trie上的一颗子树对应排序后的一个区间
然后遍历trie是如果一个节点同时存在左右子树
说明要把这两个连通块合并
把左子树的每个数都放到右子树上跑一遍来找最小异或值(作为连接连通块的边权)
然后分开遍历左右子树再加合并的权值
这样的时间复杂度是两个log的,把左子树上的放到右子树上跑是启发式的,一个log
#include<bits/stdc++.h>
#define N 200005
using namespace std;
int ch[N * 35][2], L[N * 35], R[N * 35], a[N], tot, n;
void insert(int x, int id){//插入
int p = 0;
for(int i = 30; i >= 1; i --){
int tt = x >> (i - 1) & 1;
if(!ch[p][tt]) ch[p][tt] = ++ tot;
p = ch[p][tt];
L[p] = min(L[p], id), R[p] = max(R[p], id);//确定当前节点所对应的区间
}
}
int query(int rt, int x, int dep){//找x的最小异或值
if(dep < 1) return 0;
int tt = x >> (dep - 1) & 1;
if(ch[rt][tt]) return query(ch[rt][tt], x, dep - 1);
return query(ch[rt][!tt], x, dep - 1) + (1 << (dep - 1));
}
long long dfs(int rt, int dep){
if(dep < 1) return 0;
if(ch[rt][0] && ch[rt][1]){//如果左右子树同时有就要合并
int mi = (1 << 30);
for(int i = L[ch[rt][0]]; i <= R[ch[rt][0]]; i ++) mi = min(mi, query(ch[rt][1], a[i], dep - 1));//遍历左子树的所有值,在右子树上查找,均摊是log的
return dfs(ch[rt][0], dep - 1) + dfs(ch[rt][1], dep - 1) + (1l << (dep - 1)) + mi;//左右分别做再合并
}
if(ch[rt][0]) return dfs(ch[rt][0], dep - 1);
if(ch[rt][1]) return dfs(ch[rt][1], dep - 1);
return 0;
}
int main(){
memset(L, 0x3f, sizeof L);
scanf("%d", &n);
for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
sort(a + 1, a + 1 + n);//先排序
for(int i = 1; i <= n; i ++) insert(a[i], i);
printf("%lld", dfs(0, 30));
return 0;
}