CF888G Xor-MST

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
#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;
} 

你可能感兴趣的:(字典树,贪心)