最长异或路径-trie树-题解

题目地址-P4551 最长异或路径】(POJ也有)


题意:

给你一棵树,有边权,定义两点之间的价值为这连接两点的这条路径上所有边权的异或和,如 1 → 2 1\rightarrow 2 12所经过的边权为 1 , 2 , 3 1,2,3 1,2,3,那么 1 → 2 1\rightarrow 2 12的权值就为 1 ⨁ 2 ⨁ 3 = 3 1\bigoplus2\bigoplus3=3 123=3,请你求出哪两个点的价值最大,输出这个价值。


其实,有一个非常重要的技巧,就是在异或中,相同的两个数异或起来为0,而0异或任何数字都没有影响,所以我们可以将两个点 a , b a,b a,b之间的路径的异或和看作 a → r o o t a\rightarrow root aroot的异或和异或上 b → r o o t b\rightarrow root broot的异或和(root为树的根),那么此时 l c a ( a , b ) → r o o t lca(a,b)\rightarrow root lca(a,b)root这段多余的值就被异或了两次,就消除掉影响了,所以这个答案就是 a , b a,b a,b的价值。

那么我们预处理处所有点到根的路径异或和,然后只需选两个出来,使其异或最大即可。

那么关于一堆数中选两个数出来异或最大,由于和二进制位有关,所以我们可以用01-trie树,也就是普通的trie树,字符集只有0,1。

那么举个例子:

对于 1 , 2 , 3 , 4 , 5 , 6 1,2,3,4,5,6 1,2,3,4,5,6这堆数,建出来的01-trie树就是这个样子的:

lz

所以我们接下来建出来后有两种方式求答案:

  1. 我们知道,求一个数与一堆数中的一个异或最大可以直接把这个数从高位到低位贪心的在01-trie上走一遍,就可以知道答案了,复杂度是 O ( l o g v ) O(logv) O(logv)(v为最大权值),那么将 n n n个数全部带进去走一遍就可以得到答案,复杂度是 O ( n l o g v ) O(nlogv) O(nlogv)的。
  2. 我们直接用两个指针在trie树上,从高位到低位走,贪心,每次能走相反的就走相反的,然后取最大的走出来的值即可,复杂度为 O ( n ) ∼ O ( 2 n ) O(n)\sim O(2n) O(n)O(2n)

我写的第二种方法,第一种的代码可以随便去网上找,都有。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int M=1e5+10;
int n,val[M];
struct ss{
	int to,last,len;
	ss(){}
	ss(int a,int b,int c):to(a),last(b),len(c){}
}g[M<<1];
int head[M],cnt,maxv,lg;
void add(int a,int b,int c){
	g[++cnt]=ss(b,head[a],c);head[a]=cnt;
	g[++cnt]=ss(a,head[b],c);head[b]=cnt;
}
void dfs(int a,int b){
	if(val[a]>maxv)maxv=val[a];
	for(int i=head[a];i;i=g[i].last){
		if(g[i].to==b) continue;
		val[g[i].to]=val[a]^g[i].len;
		dfs(g[i].to,a);
	}
}
int son[2][M<<4],tot;
void insert(int a){
	int now=0;
	for(int i=lg;i>=0;i--){
		int id=((a>>i)&1);
		if(!son[id][now])son[id][now]=++tot;
		now=son[id][now];
	}
}
int ans;
void find(int a,int b,int v,int bit){
	//a,b表示当前走到的两个节点,v是已经获得的值,bit是当前可获得的值是1<<bit
	if(v>ans)ans=v;
	if(a==b){//同一个节点直接走(1,0),(0,1)最优,没有的话就走那剩余的一个节点
		if(son[0][a]&&son[1][a]){
			find(son[0][a],son[1][a],v|(1<<bit),bit-1);
		}else{
			int w=son[0][a]|son[1][b];
			if(w)find(w,w,v,bit-1);
		}
	}else{
		bool had=0;
		if(son[0][a]&&son[1][b]){
			had=1;
			find(son[0][a],son[1][b],v|(1<<bit),bit-1);
		}
		if(son[1][a]&&son[0][b]){
			had=1;
			find(son[1][a],son[0][b],v|(1<<bit),bit-1);
		}
		//走(1,0),(0,1),如果没法的话再走(1,1),(0,0),这里是个贪心
		if(!had){
			if(son[0][a]&&son[0][b]){
				find(son[0][a],son[0][b],v,bit-1);
			}
			if(son[1][a]&&son[1][b]){
				find(son[1][a],son[1][b],v,bit-1);
			}
		}
	}
	//由于最多会有2n个节点,最坏的情况是将其全部遍历一次,所以复杂度为O(n)的
}
int a,b,c;
int main(){
	scanf("%d",&n);
	for(int i=1;i<n;i++){
		scanf("%d%d%d",&a,&b,&c);
		add(a,b,c);
	}	dfs(1,0);
	for(lg=1;(1ll<<lg)<maxv;++lg);//小心1<<32爆int,我开始写错了TLE了好几次
	for(int i=1;i<=n;i++)insert(val[i]);//但是遗憾的是构建的复杂度还是O(nlogv)的
	find(0,0,0,lg);
	printf("%d\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

VictoryCzt

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值