题意:给出一颗树,每棵树上有n个结点,每个结点对应一个值,有m组查询操作,查询在从x到y的这条路径上与z进行异或的最大值。
分析:可持久化字典树的模板题
这个题以01字典树为基础,如果不是很了解01字典树的话,可以看看ACdream1063,这题题解。
话说回来,如果我们要查询数x与某个数异或的最大值,我们应该尽量选择高位与x的高位相反的一些数。
举个栗子,我们要求5与某个数异或的最大值,假设可选的数不超过15,5的二进制为0101,因为可选的数不超过15,所以我们只要考虑后四位,
首先看最高位(第四位)存不存在等于1的数,存在的话,一直往低位走,最终可以得到结果。
这个题就要对每个结点都建一棵Trie树(感觉根主席树有点像噢,主席树是对每个结点建线段树
建Trie树的时候,我们用一个数组sz[]记录字典树对应结点前缀的数量,如果v是u的子节点,且v的权值是010,u的权值为011,假设u已经加到树中,那么在加v的时候,发现前缀01的数量已经是1了,那我们只要在这个基础上加1即可,也就是继承了父节点对它的影响,但是对于010来说是一个新的数,我们要新建一个结点然后更新他的sz,其余和父节点一致就好。
当我们去查询每一位都取反的x时,只关心每一位是否对应存在。也就是想知道u到v这条路上有没有满足条件的存在。
设pre=lca(u,v),我们就只要判断f(u)+f(v)-2×f(pre)是否大于0就好,大于0的话,就加上1>>i。
参考代码:
/*可持久化字典树*/
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#include<iostream>
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
const int maxn = 1e5+10;//原树结点
const int maxv = 16;//每颗字典树的深度
const int maxnode = 2e6;//字典树的结点个数,16*1e5=1600000
int n,q;
int a[maxn];
vector<int> g[maxn];
//字典树
int tot;//记录字典树的总结点数
int root[maxn];//记录字典树的根节点
int f[maxv+2][maxn];//嘻嘻嘻嘻f[i][v]指的是结点v向上跳2^i次后得到的结点值
int sz[maxnode];//记录前缀和
int dep[maxn];//记录结点深度
int ch[maxnode][2];//字典树儿子结点
void Init()
{
tot = 0;
mem(root,0);
mem(f,0);
mem(sz,0);//前缀和初始化为0
dep[0] = 0;
}
int NewNode()
{
mem(ch[++tot],0);
return tot;
}
void Insert( int u, int fa, int val)
{
int rtu = root[u];//root[u]是字典树中真正的位置,而u是在原树中的编号
int rtf = root[fa];//同理
for( int i = 15; i >= 0; i--)
{
int id = (val>>i)&1;//计算val的第i位(有第0位)是1还是0
if( !ch[rtu][id])//如果第i位不存在的话,那就要新建一个结点
{
ch[rtu][id] = NewNode();
ch[rtu][!id] = ch[rtf][!id];//!id就继承父节点的
sz[ch[rtu][id]] = sz[ch[rtf][id]];//前缀也继承父节点的
}
rtu = ch[rtu][id];//往下走
rtf = ch[rtf][id];
sz[rtu]++;
}
}
//遍历原树中的n个结点,对每个结点建立01字典树
void dfs( int u, int fa)
{
f[0][u] = fa;//记录下u的父节点
dep[u] = dep[fa]+1;//当前结点的深度为其父亲的深度+1
root[u] = NewNode();//给字典树新建一个结点
Insert(u,fa,a[u]);//根据结点u对应的值建01字典树
for( int i = 0; i < g[u].size(); i++)//遍历当前结点u的叶子结点
{
int v = g[u][i];
if( v != fa)//如果不是其父节点,继续深搜
dfs(v,u);
}
}
//倍增lca
//求u和v的最近公共祖先
int lca( int u, int v)
{
if( dep[u] > dep[v])//保证u的深度小于v,也就是u和v的祖先结点在同一深度
swap(u,v);
for( int i = 0; i < maxv; i++)
if( ((dep[v]-dep[u])>>i) & 1)//如果条件为真,就往上跳,最终结果会与u在同一层
v = f[i][v];
if( u == v)//显然
return u;
for( int i = maxv-1; i >= 0; i--)
if( f[i][u] != f[i][v])//还不相等的话,就一起往上走
u = f[i][u], v = f[i][v];
return f[0][u];//最后u==v,返回自己
}
int Query( int u, int v, int val)
{
int f = lca(u,v);//求u,v的最近公共祖先
int res = a[f]^val;
int rtu = root[u], rtv = root[v], rtf = root[f];//转化到字典树中去
int ret = 0;
for( int i = maxv-1; i >= 0; i--)
{
int id = (val>>i)&1;
if( sz[ch[rtu][!id]]+sz[ch[rtv][!id]]-2*sz[ch[rtf][!id]] > 0)//满足这个条件
{
ret += 1<<i;
id = !id;
}
rtu = ch[rtu][id];
rtv = ch[rtv][id];
rtf = ch[rtf][id];
}
return max(ret,res);
}
int main()
{
while( ~scanf("%d%d",&n,&q))
{
Init();
for( int i = 1; i <= n; i++)
{
scanf("%d",&a[i]);//输入每个结点的值
g[i].clear();//清空动态数组
}
int u,v;
for( int i = 1; i < n; i++)
{
scanf("%d%d",&u,&v);//输入一条边
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1,0);
for( int i = 0; i < maxv-1; i++)
for( int j = 1; j <= n; j++)
if( f[i][j] != 0)
f[i+1][j] = f[i][f[i][j]];//j向上跳2^(i+1)次得到的结点相当于j先向上跳2^i再向上跳2^i
int x,y,z;
while( q--)
{
scanf("%d%d%d",&x,&y,&z);
printf("%d\n",Query(x,y,z));
}
}
return 0;
}