【CF888G】 Xor-MST 简易题解

题目大意

         \ \ \ \ \ \ \,       给你一个 n n n 个节点的完全图,第 i i i 个点的权值为 a i a_i ai ,两点的之间边权为这两个点权值的异或值,求最小生成树的权值。

想法

         \ \ \ \ \ \ \,       其实这道题没有那么复杂,还是好想的。

         \ \ \ \ \ \ \,       最小生成树的话,我们显然有一个基于贪心的 K r u s k a l Kruskal Kruskal 算法,复杂度 O ( n 2 log ⁡ n ) O(n^2\log n) O(n2logn),想想还是算了吧。

         \ \ \ \ \ \ \,       而遇到关于异或的题呢,我们一般会有两种想法:整形异或线性基, T r i e Trie Trie 树。

         \ \ \ \ \ \ \,       容易想到的,这道题当然和线性基没有关系了,我们思考一下 T r i e Trie Trie 树,首先,我们先把第一个样例从高位到低位插入线性基看看:

在这里插入图片描述

         \ \ \ \ \ \ \,       容易发现,对于每个叶子节点,既每个点值之间,要是需要互相连边,那么求 他们 L c a Lca Lca 以后的边的亦或值 即可。

         \ \ \ \ \ \ \,       由此可得,若是 L c a Lca Lca 的深度越深,便约优。因为我们是从高位到低位插入的,所以浅的点权值较大,要尽量避免选择浅的点。

         \ \ \ \ \ \ \,       我们不妨把可能是 L c a Lca Lca 的点拉出来瞅瞅:

在这里插入图片描述
         \ \ \ \ \ \ \,       惊喜地发现,刚好有 4 4 4 个点,也就是所有拥有两个儿子的点一共有 4 4 4 个,可以证明,如果 a i a_i ai 两两不等的话,那么这种点一共有 n − 1 n-1 n1 个,那么答案就呼之欲出了:

         \ \ \ \ \ \ \,       我们每找到这样的点,就暴力贪心 D F S DFS DFS 下去:

  • 每次尽量同时走左儿子或右儿子;
  • 如果两个都有,就两个都走,然后返回值取 m i n min min
  • 如果两个只有不一样的儿子,就在返回值加上这一深度 b i t bit bit的值,然后继续走

         \ \ \ \ \ \ \,       最终答案就是他们的 D F S DFS DFS 值的和。

         \ \ \ \ \ \ \,       那如果 a i a_i ai 不是两两不等的话怎么办呢,如果 a u = a v a_u=a_v au=av 的话,我们当然首先建一条边连接 u u u v v v,权值为 0 0 0,对答案完全没有影响,所以我们正常建,正常搜,是不会有问题的。

#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cstdio>
#include<vector>
#include<string>
#include<queue>
#include<stack>
#include<cmath>
#include<map>
#include<set>
using namespace std;
const int inf=0x7fffffff;
const double eps=1e-10;
const double pi=acos(-1.0);
//char buf[1<<15],*S=buf,*T=buf;
//char getch(){return S==T&&(T=(S=buf)+fread(buf,1,1<<15,stdin),S==T)?0:*S++;}
inline int read(){
	int x=0,f=1;char ch;ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-') f=0;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch&15);ch=getchar();}
	if(f)return x;else return -x;
}
struct Trie{
	int son[2][200000*30+10],tot;
	void Insert(int a){
		int now=0,id;
		for(int i=30;i>=0;i--){
	    	id=(a>>i)&1;
	    	if(!son[id][now])son[id][now]=++tot;
	    	now=son[id][now];
		}
	}
	int Find(int r1,int r2,int b){
		if(b<0) return 0;
		int a1=-1,a2=-1;
		if(son[0][r1]&&son[0][r2]) a1=Find(son[0][r1],son[0][r2],b-1);
		if(son[1][r1]&&son[1][r2]) a2=Find(son[1][r1],son[1][r2],b-1);
		if(~a1&&~a2) return min(a1,a2);
		if(~a1) return a1;if(~a2) return a2;
		if(son[1][r1]&&son[0][r2]) a1=Find(son[1][r1],son[0][r2],b-1)+(1<<b);
		if(son[0][r1]&&son[1][r2]) a2=Find(son[0][r1],son[1][r2],b-1)+(1<<b);
		if(~a1&&~a2) return min(a1,a2);
		if(~a1) return a1;if(~a2) return a2;
	}
}T;
long long ans;
void dfs(int a,int b){
	if(b<0) return;
	if(T.son[0][a]&&T.son[1][a]) ans+=1ll*T.Find(T.son[0][a],T.son[1][a],b-1)+(1ll<<b);
	if(T.son[0][a]) dfs(T.son[0][a],b-1);
	if(T.son[1][a]) dfs(T.son[1][a],b-1);
}
int n,v;
int main()
{
	n=read();
	for(int i=1;i<=n;i++)T.Insert(read());
	dfs(0,30);
	printf("%I64d\n",ans);
	return 0;
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值