【Xor-MST,CF888G】(字典树,Boruvka算法)

题目

思路

主要思路都写在了代码的注释里

为什么要按照异或值给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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值