CF888G Xor-MST

CF888G Xor-MST

Boruvka 算法求最小生成树:

其实我们发现,一个点的连边中权值最小的一定在最小生成树中,Boruvka 算法就是利用了这个思想,每次将每个点的连边中权值最小的加入最小生成树,然后把几个相连的点合并为一个大点,由于每次的点至少会两两配对,所以最多合并 \(\log n\) 次,总时间为 \(\mathcal{O}(T\log n)\)\(T\) 表示求解每个点的最小连边的时间。

在这道题中,我们就可以利用这个思想,至于如何快速求解最小连边呢?

由于边权为点权异或而来,所以我们可以建立一个 01-Trie,对于一个深度为 \(d\) 的子树,子树内的边权都小于 \(2^d\),但子树外的边权显然至少是 \(2^d\)

或者说,两个点在 Trie 树上的 LCA 越深,边权就一定更小。 因此我们一定是从下往上加边:先把子树内合并,然后子树和它的兄弟合并,再继续向上合并。

我们在Tire树上从浅到深考虑,设当前考虑到了 \(2^d\) 位,若左右儿子都有,我们完成对其左右儿子的合并,然后继续递归下去。

至于对左右儿子的合并,我们可以依次从高到低位考虑,设当前合并 \(u,v\) 两个节点,先看能否把他们的左儿子和左儿子合并,右儿子和右儿子合并,否则左右儿子交替合并,若只有一个节点有儿子了,直接不管了。

#include<bits/stdc++.h>
#define p_b push_back
#define m_p make_pair 
#define fi first
#define se second
#define ll long long
using namespace std;
inline int rd(){
	int x=0,f=1; char ch=getchar();
	for(;ch<'0'||ch>'9';ch=getchar())if (ch=='-') f=-1;
	for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
	return x*f;
}
const int N=2e5;
const ll INF=0x3f3f3f3f3f3f3f3f;
int n;
struct Trie{
	int son[2][N*31],tot;
	void insert(int x){
		int now=0,id;
		for(int i=30;i>=0;i--){
			id=(x>>i)&1;
			if(!son[id][now]) son[id][now]=++tot;
			now=son[id][now];
		}
	}
	ll find(int u,int v,int d){
		if((!son[0][u]&&!son[1][u])||(!son[0][u]&&!son[1][u])||d<0) return 0;
		ll x=INF,y=INF;
		if(son[0][u]&&son[0][v])x=find(son[0][u],son[0][v],d-1);
		if(son[1][u]&&son[1][v])y=find(son[1][u],son[1][v],d-1);
		if(x!=INF||y!=INF) return min(x,y);
		if(son[0][u]&&son[1][v])x=find(son[0][u],son[1][v],d-1)+(1ll<<d);
		if(son[1][u]&&son[0][v])y=find(son[1][u],son[0][v],d-1)+(1ll<<d);
		return min(x,y);
	}
}T;
ll ans;
void dfs(int now,int d){
	if(d<0) return;
	if(T.son[0][now]&&T.son[1][now])ans+=T.find(T.son[0][now],T.son[1][now],d-1)+(1ll<<d);
	if(T.son[0][now]) dfs(T.son[0][now],d-1);
	if(T.son[1][now]) dfs(T.son[1][now],d-1);
}
signed main(){
	n=rd();	
	for(int i=1;i<=n;i++) T.insert(rd());
	dfs(0,30);
	printf("%lld\n",ans);
	return 0;	
}
posted @ 2024-07-19 16:37  123456xwd  阅读(46)  评论(0)    收藏  举报