题目大意:有一棵n个点的树,树上每条边都有边权,求任意两点之间的简单路径的边权的异或和最大是多少
1<=n<=1e5
思路:因为异或和可以忽略重合的部分,所以两个点之间的路径的异或和就是每个点到他们的公共组先的异或和,那么我们直接从第一个节点出发dfs记录每一个点i(包括第一个点)到第一个点的异或和dx[i],然后我们只需要在所有的dx中找出异或值最大的两个点,用字典树可以做到O(n),trie[i][j]表示二进制从左到右忽略公共前缀的第i位数字是j,对于一个要查询的dx[i],我们在字典树中优先查找与每一位数相反的前缀,得到的便是最大值
边权树如上图,可得出0到各点的异或和dx为0,7,5
字典树(2的0次方到2次方部分)如上图
#include<__msvc_all_public_headers.hpp>//vs代码
using namespace std;
const int N = 1e5 + 5;
int head[N],trie[N*32][2];
int n, dx[N];
struct node
{
int v, next, w;
}e[N << 1];
int cnt = 0;
void addedge(int u, int v, int w)
{//链式前向星存图
e[++cnt].v = v;
e[cnt].w = w;
e[cnt].next = head[u];
head[u] = cnt;
}
void init()
{
memset(e, 0, sizeof e);
memset(head, 0, sizeof head);
memset(trie, 0, sizeof trie);
memset(dx, 0, sizeof dx);
}
void dfs(int u, int fa)
{//遍历每个点,求出每个点到0点的异或和
for (int i = head[u]; i; i = e[i].next)
{
int v = e[i].v, w = e[i].w;
if (v == fa)//防止重复遍历
continue;
dx[v] = dx[u] ^ w;
dfs(v, u);
}
}
int tot = 1;
void build(int num)
{//构建字典树
int p = 1;
for (int i = 30; i >= 0; i--)//数据范围0~2^31
{
bool k = num & (1 << i);//判断二进制的每一位为0或1
if (!trie[p][k])
trie[p][k] = ++tot;
p = trie[p][k];
}
}
int query(int num)
{//对于每个dx[i],求出他和其他所有数异或的最大值
int p = 1, ret = 0;
for (int i = 30; i >= 0; i--)
{
bool k = num & (1 << i);
if (trie[p][k ^ 1])//如果这一位有与当前查询数字相反的,优先走这条路
{
ret += 1 << i;//答案+
p = trie[p][k ^ 1];
}
else
{
p = trie[p][k];
}
}
return ret;
}
int main()
{
while (~scanf_s("%d", &n))
{
init();
for (int i = 1; i <= n - 1; i++)
{
int u, v, w;
scanf_s("%d%d%d", &u, &v, &w);
addedge(u, v, w);
addedge(v, u, w);
}
dfs(0, 0);
for (int i = 0; i <= n - 1; i++)
{
build(dx[i]);
}
int ans = 0;
for (int i = 0; i <= n - 1; i++)
{
ans = max(ans, query(dx[i]));//维护答案最大值
}
printf("%d\n", ans);
}
return 0;
}