洛谷题单指南-字符串-P4551 最长异或路径
原题链接:https://www.luogu.com.cn/problem/P4551
题意解读:求树中两个结点的最长异或路径。由于异或路径指的是指两个结点之间唯一路径上的所有边权的异或,而两个结点路径之间所有边权的异或又等同于两个结点各自到根节点所有边权的异或再求异或,这是因为如果两个结点到根结点最后一段是公共路径,那么公共路径异或之后得0,与两个结点路径边权异或结果不变。
解题思路:
1、设1号结点为根结点,先计算所有结点到根节点的路径边权异或,记为x[i]。
直接dfs所有结点,dfs过程中进行异或计算即可。
对于样例数据:
4
1 2 3
2 3 4
2 4 6
计算得到x[1] = 0,x[2] = 3,x[3] = 3^4 = 7,x[4] = 3^6 = 5
2、求x[]中两个数异或的最大值。
计算一组数中两个数最大异或值,是一个经典问题。
可以将每个数的31位二进制存入Trie树,对于0(二进制000),3(二进制011),7(二进制111),5(二进制101),为方便图示,二进制只统一到3位长度:
然后对于每一个数,在Trie中去查找与其异或最大的值,例如要计算与3(011)异或最大的值,沿着Trie树查找尽量跟001每一位相反的路径才能保证得到最大的异或值,因为0和1异或才会得1,相同的数异或得0,这样一来,在Trie树中边查找的过程就可以一边计算异或后得到的结果,取最大值即可。
100分代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
struct Node
{
int v, w;
};
vector<Node> tr[N]; //邻接表建树
int x[N]; //x[i]表示结点i到根节点路径权值的异或
int son[N * 31][2], idx; //Trie树
int n, u, v, w;
int ans;
//从根结点开始计算每个结点的x[i]值
void dfs(int u, int val)
{
x[u] = val;
for(Node node : tr[u])
{
dfs(node.v, val ^ node.w);
}
}
//添加val到Trie树,val的31位二进制
void add(int val)
{
int u = 0;
for(int i = 31; i >= 0; i--)
{
int v = (val >> i) & 1; //从高到低取val的二进制位
if(!son[u][v]) son[u][v] = ++idx;
u = son[u][v];
}
}
//在Trie树中计算与val异或最大的值
void find(int val)
{
int u = 0, res = 0;
for(int i = 31; i >= 0; i--)
{
int v = (val >> i) & 1; //从高到低取val的二进制位
if(son[u][!v]) //如果与当前二进制位相反的路径存在就优先走这条路径
{
u = son[u][!v];
res = 2 * res + 1; //相反的二进制异或得1,整体结果上要加上二进制1产生的贡献
}
else if(son[u][v])
{
u = son[u][v];
res = 2 * res; //相同二进制异或得0,整体结果上要加上二进制0产生的贡献
}
}
ans = max(ans, res);
}
int main()
{
cin >> n;
for(int i = 1; i < n; i++)
{
cin >> u >> v >> w;
tr[u].push_back({v, w});
}
dfs(1, 0);
for(int i = 1; i <= n; i++) add(x[i]);
for(int i = 1; i <= n; i++) find(x[i]);
cout << ans;
return 0;
}