建立字典树是异或的一种处理方法。
题解:
题目要求找一条路径使得这条路径上的每条边异或和最大。对于朴素算法我们可以预处理树上前缀异或和,通过求任意两点的LCA,并把两条路径再异或得到答案更新(异或是有结合律的)。这样的时间复杂度是$O(n^2\log n)$。
如果看出异或就是异或的逆运算,也就是$x\bigoplus y\bigoplus y=x$,就可以知道,我们直接将两点的树上异或前缀和再次异或,就相当于消去了二者LCA以上的部分,得出的结果就是两者唯一路径上的异或和。LCA以上的部分实际上是异或了一遍又异或回来了。
此时我们只要枚举两个点,让它们的前缀和相异或就可以了,时间复杂度为$O(n^2)$。但是$latex 100,000$的数据需要更加优化。
我们可不可以先存下所有点的异或前缀和,然后枚举每个点,在$O(1)$或$O(\log n)$的时间复杂度内找出已经存下的点的前缀和中与当前点的前缀和异或和最大的来更新答案呢?
尽管与一个数的异或不满足任何RMQ问题的解法,但是我们是不是可以贪心呐?对于一个二进制数,我们要使得一个数与它的异或值最大,就要按位从大到小尽可能不同。也就是贪心的思想,即$(10000000)_2>(01111111)_2$。
101001001 (1)
110100100 (2)
100110011 (3)
因为(2)与(1)在第6位(最右边是第0位)就出现了不同,因此(1)跟(2)匹配是要比(3)优的。
那我们要找到与一个数异或最大的,就可以把这个数给转成二进制,按位次从高到低建立字典树,深度越浅,贪心程度越大。因此贪心得出的结果总是唯一最优的。这里用了动态开点,不然找到了不存在的数,计算就会出现错误。
这棵字典树也就是一棵二叉树,我们就要尽可能朝与当前位相反的地方走;如果没有相反的就只好顺着走了。并且因为每条链深度都是31的,所以不用担心访问到空节点。
Code:
#include<cstdio>
#include<cstring>
struct node
{
int down;
node *ls,*rs;
node()
{
ls=NULL;
rs=NULL;
}
void build(int x,int k)//开点的过程
{
if(k==-1)
return;
if((x>>k)&1)
{
if(!rs)
rs=new node();
rs->build(x,k-1);
}
else
{
if(!ls)
ls=new node();
ls->build(x,k-1);
}
}
int inv(int x,int k)//求尽可能多的相反能造成多少贡献
{
if(k<0)
return 0;
if((x>>k)&1)
{
if(ls)
return (1<<k)+ls->inv(x,k-1);
else
return rs->inv(x,k-1);
}
else
{
if(rs)
return (1<<k)+rs->inv(x,k-1);
else
return ls->inv(x,k-1);
}
}
}*root;
struct edge//建树
{
int n,v,nxt;
edge(int n,int nxt,int v)
{
this->n=n;
this->v=v;
this->nxt=nxt;
}
edge()
{
nxt=-1;
} }e[220000];
int head[100100],ecnt=-1;
void add(int from,int to,int v)
{
e[++ecnt]=edge(to,head[from],v);
head[from]=ecnt;
e[++ecnt]=edge(from,head[to],v);
head[to]=ecnt;
}
int sum[100100];
void dfs(int x,int from)
{
for(int i=head[x];~i;i=e[i].nxt)
if(e[i].n!=from)
{
sum[e[i].n]=(sum[x]^e[i].v);
dfs(e[i].n,x);
}
return;
}
int main()
{
root=new node();
memset(head,-1,sizeof(head));
int n,u,v,w;
scanf("%d",&n);
for(int i=1;i<n;++i)
{
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
}
sum[1]=0;
dfs(1,1);
for(int i=1;i<=n;++i)
root->build(sum[i],30);
int ans=0;
for(int i=1;i<=n;++i)
{
int tmp=root->inv(sum[i],30);
ans=ans>tmp?ans:tmp;
}
printf("%d\n",ans);
return 0;
}