题目大意: 给一棵有边权的树,求树上的最大异或和路径。
题解
需要巧妙地用到异或的一个性质: a x o r b = c , c x o r b = a a~xor~b=c~,~c~xor~b=a a xor b=c , c xor b=a,即 a x o r b x o r b = a a~xor~b~xor~b=a a xor b xor b=a。
设 d i s ( x , y ) dis(x,y) dis(x,y) 为 x x x 到 y y y 路径的异或和,那么有: d i s ( x , y ) = d i s ( 1 , x ) x o r d i s ( 1 , y ) dis(x,y)=dis(1,x)~xor~dis(1,y) dis(x,y)=dis(1,x) xor dis(1,y)。
发现所有 d i s ( 1 , i ) dis(1,i) dis(1,i) 是可以 O ( n ) O(n) O(n) 求出来的,问题转化成找到两个异或和最大的 d i s ( 1 , x ) dis(1,x) dis(1,x) 和 d i s ( 1 , y ) dis(1,y) dis(1,y)。
考虑贪心,像线性基的求最大异或和类似(如果没学过线性基可以忽略这句话),我们将所有 d i s ( 1 , i ) dis(1,i) dis(1,i) 的二进制数造一棵字典树,然后对于每一个 d i s ( 1 , i ) dis(1,i) dis(1,i),我们都用来求一次答案,从最高位开始贪。因为异或运算的规则是:假如两者的对应位置不一样,那么运算结果的这一位就是 1 1 1,所以我们从最高位开始,尽可能找和当前 d i s ( 1 , i ) dis(1,i) dis(1,i) 的这一位不同的数,假如有,那么就往字典树的那边走,同时答案的这一位变成 1 1 1,否则就往另一边走。
代码如下:
#include <cstdio>
#include <cstring>
#define maxn 100010
struct node{
node *son[2];
node(){son[0]=son[1]=NULL;}
};
node *root=new node();
int n;
struct edge{int y,z,next;};
edge e[maxn*2];
int first[maxn];
void buildroad(int x,int y,int z)
{
static int len=0;
e[++len]=(edge){y,z,first[x]};
first[x]=len;
}
int ans=0;
void check(int x)
{
node *now=root;int sum=0;
if(root->son[0]==NULL&&root->son[1]==NULL)return;
for(int i=30;i>=0;i--)
{
int p=1;
if((x&(1<<i))>0)p=0;
if(now->son[p]!=NULL)sum^=(1<<i),now=now->son[p];
else now=now->son[p^1];
}
if(sum>ans)ans=sum;
}
void add(int x)
{
node *now=root;
for(int i=30;i>=0;i--)
{
int p=0;
if((x&(1<<i))>0)p=1;
if(now->son[p]==NULL)now->son[p]=new node();
now=now->son[p];
}
}
void dfs(int x,int fa,int dis)
{
for(int i=first[x];i;i=e[i].next)
{
int y=e[i].y;
if(y==fa)continue;
check(dis^e[i].z);add(dis^e[i].z);
dfs(y,x,dis^e[i].z);
}
}
int main()
{
scanf("%d",&n);
for(int i=1,x,y,z;i<n;i++)
scanf("%d %d %d",&x,&y,&z),buildroad(x,y,z),buildroad(y,x,z);
dfs(1,0,0);
printf("%d",ans);
}