poj 3764字典树应用

题目大意:给定棵树,两节点间有权值,求一条路径,边上的权值异或值最大,并输出这个最大的异或值。

因为   (a^c)^(c^b)=a^b ; 相同的部分c抵消掉,在树中,如果求出两点到根的异或之和A,B ,则两点间的异或之和就等于A^B ,A和B里都有相同的部分c ;

要求的是最大的这样的路径是多少。讲到树上的两点的xor,一个常用的手段就是随便选一个根,然后深搜记下每个点v到根的xor路径 w[v],那么两点x,y路径的xor就是w[x]^w[y].

深搜一发,问题转化为给你一个数组a,求a中哪两个数的抑或值最大。解决该问题的方法就是Trie树。

对每个权值由二进制高位到低位插到Trie树里,当要询问对于权值x最大的xor的时候,就需要从树上贪心的去匹配,譬如x的高位是1,那么我们就希望从Trie树上往0走,否则的话我们希望往1走,因为1^0=1,0^0=0,1^1=1 ,所有不同的异或才尽可能最大。并且从高位开始异或匹配才尽可能的大,这个在Trie树中是很容易实现的。在本题中n个数,最大是2^31-1,所以需要的节点的数量最多可以到到达32n。我们做的时候是询问一个数,插一个数,每次询问的复杂度也是O(32),所以总复杂度是O(32n);


#include<cstdio>
#include<cstring>
#include<map>
#include<vector>
#include<cmath>
#include<cstdlib>
#include<stack>
#include<queue>
#include <iomanip>
#include<iostream>
#include<algorithm>
using namespace std ;
const int N=200010 ;
struct node
{
	int to,w,next;
}edge[N];

struct Trienode
{
	Trienode *next[2] ;
}T[N*32],*root ;
	
int top ,cnt ,head[N],bit[32],cost[N];

void add(int u ,int v, int w )
{
	 edge[top].to =v ;
	 edge[top].w=w;
	 edge[top].next=head[u] ;
	 head[u]=top++;
	 
	 edge[top].to=u;
	 edge[top].w=w;
	 edge[top].next=head[v];
	 head[v]=top++;
}

void dfs(int u,int num,int fa)
{
	cost[u]=num;
	for(int i=head[u] ; i!=-1;i=edge[i].next)
	{
		int v = edge[i].to ;
		   if(v!=fa)
		   {
		   	     dfs(v,num^edge[i].w,u);
		   }
		
	}
}

void insert(int x)
{
    Trienode *p=root ; 
    for(int i = 30 ; i>= 0 ; i--)//从高位开始插进去 
    {
    	  int id= x&bit[i] ? 1:0 ;//找出插入的数的每一位是1还是0 ; 
    	  if(p->next[id] != NULL)//有点了,继续 
		  {
		  	    p=p->next[id] ;
		  	
		  }
		  else
		  {
		  	    p->next[id]=&T[cnt];//创建新节点 
		  	    T[cnt].next[0]=T[cnt].next[1]=NULL ;//初始化 
		  	    cnt++;
		  	    p=p->next[id] ;
		  } 
    }
} 

int find(int x)
{
	Trienode *p=root ;
	int ans=0;
	for(int i = 30 ; i >= 0 ; i--)
	{
	      int id= x&bit[i] ? 1:0 ;
		  if(p->next[id^1]!=NULL)//贪心策略,1^0=1  
		  {
		  	   ans += bit[i] ; //异或后的这一位,把结果加上 
		  	   p=p->next[id^1];
		  } 
	      else
	         p=p->next[id] ;
	} 
	return ans ;
}

int main()
{
	bit[0]=1;    // 二进制每位的数 2^i ; 
	for(int i = 1 ; i < 32 ; i++)
	     bit[i]=bit[i-1]*2 ;
	 int  n ;    
	while(cin>>n)
	{
		  int u,v,w ;
		  memset(head,-1,sizeof(head)) ;
		  top=0;cnt=0;
		  for(int i = 1 ; i < n ; i++)
		  {
		  	  scanf("%d%d%d",&u,&v,&w);
		  	  add(u,v,w);
		  }
		  dfs(0,0,-1);//深搜,算出所有节点到根节点的异或之和 
		  root = &T[cnt] ;//字典树的根节点 
		  T[cnt].next[0]=T[cnt].next[1]=NULL ;//两个子节点初始化(代表0,1节点) 
		  cnt++;
		  insert(cost[0]) ;//先插入第一个节点; 
		  int ans = 0 ;
		  for(int i = 1 ; i< n ; i++)
		  {
		  	     int tmp = find(cost[i]) ;//查询该数与之前插入的数的最大的异或和, 
		  	     ans = ans > tmp ? ans:tmp ; //更新最大异或和 
		  	   insert(cost[i]);  //查询完插入该数 
		  }
		printf("%d\n",ans); 
	}
	return 0;
} 



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值