牛客多校 B Graph 分治+trie求完全图的生成树

这题是51nod上一个题目稍微变形了一下:完全图最小生成树

原题是 对于一个完全图 每条边(u,v)的边权都是 a[u]^a[v]   求一个最小生成树  

但是如果暴力去做的话  肯定会爆时间  n=1e5  边数就是n的平方级别的    我们需要去优化建立生成树的过程   

 有一个明显的想法是  从最高位开始  我们把当前位为0的分成一块  为1的分成一块  那么肯定是所有为1的在一个联通块内  所有为0的在一个联通块内   因为这样的话  为1的联通块的边权会尽量小  为0的联通块也是

那么要连接这两个联通块  就需要在他们之间 建立一条边   显然我们可以先把 为0的联通块的点权放入trie  然后再遍历为1的联通块的点权  得到一个最小异或值 即可  

这样的话我们可以得到一个分治思路   从最高位开始  每次分成01 两块  在这两块中建立最小边权   在递归解决两个子问题即可 然后这道题还需要计数   计数的话有个公式  完全图的生成树个数为 :  n^(n-2) 种   

不过牛客的题没有计数  会简便很多  每次加上最小边权就可以了

复杂度是 n(logn^2) 的

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+100;
int h[N],nex[N<<1],to[N<<1],edge[N<<1];
int trie[N*32][2],tot,a[N],n,s[N],t[N],cur;
const int inf=(1<<30);
typedef long long ll;
ll sum;
inline int in(){
	int x=0;char c=0;
	while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return x; 
}
void add_edge(int x,int y,int z){
	to[++cur]=y;nex[cur]=h[x];edge[cur]=z;h[x]=cur;
}
void dfs(int u,int f){
	for(int i = h[u]; i; i = nex[i]){
		int v = to[i];
		if(v==f) continue;
		a[v]=a[u]^edge[i];
		dfs(v,u);
	}
}
void init(){
	for(int i = 0; i <= tot; i++) trie[i][0]=trie[i][1]=0;
	tot=0;
}
void ins(int x){
	int p=0;
	for(int i = 29; i >= 0; i--){
		int c=(x>>i)&1;
		if(!trie[p][c]) trie[p][c]=++tot;
		p=trie[p][c];
	}
}
inline int getmi(int x){
	int ans=0,p=0;
	for(int i = 29; i >= 0; i--){
		int c = (x>>i)&1;
		if(trie[p][c]) ans|=c<<i,p=trie[p][c];
		else ans|=(c^1)<<i,p=trie[p][c^1];
	}
	return (ans^x);
}
void solve(int st,int ed,int now){
	if(st>=ed) return;
	if(now<0) return;
	int cnt1=0,cnt2=0;
	for(int i = st; i <= ed; i++)
	if((a[i]>>now)&1) s[++cnt1]=a[i];
	else t[++cnt2]=a[i];
	for(int i = 1; i <= cnt1; i++) a[i+st-1]=s[i];
	for(int i = 1; i <= cnt2; i++) a[st+cnt1+i-1]=t[i];
	init();
	for(int i = 1; i <= cnt2; i++) ins(t[i]);
	int mi=inf;
	if(cnt2){
		for(int i = 1; i <= cnt1; i++){
			int k=getmi(s[i]);
			mi=min(mi,k);
		}
	}
	if(mi!=inf) sum+=mi;
	solve(st,st+cnt1-1,now-1);solve(st+cnt1,ed,now-1);
}
int main(){
	n=in();
	for(int i = 1; i < n; i++){
		int u,v,w;
		u=in(),v=in(),w=in();
		u++,v++;
		add_edge(u,v,w);
		add_edge(v,u,w);
	}
	dfs(1,0);
	solve(1,n,29);
	printf("%lld\n",sum);
	return 0;
} 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值