最长异或路径(Tire树+贪心)
题目描述
给定一棵 nn 个点的带权树,结点下标从 1 开始到 n。寻找树中找两个结点,求最长的异或路径。
异或路径指的是指两个结点之间唯一路径上的所有边权的异或。
输入格式
第一行一个整数 n,表示点数。
接下来 n-1 行,给出 u,v,w ,分别表示树上的 u 点和 v 点有连边,边的权值是 w。
输出格式
一行,一个整数表示答案。
输入输出样例
输入 #1
4
1 2 3
2 3 4
2 4 6
输出 #1
7
解题思路
首先,异或有一个性质
A ⨁ A = 0 ⇒ A ⨁ B ⨁ A = B A \bigoplus A = 0 \Rightarrow A \bigoplus B \bigoplus A = B A⨁A=0⇒A⨁B⨁A=B
要求两个节点之间的路径上所有的边权异或和,可以先预先处理出每个节点到根节点的所有边权的异或和。
根据如上性质,在计算两个节点的异或和时等价于求两个节点到根节点所有边权的异或和做异或,可以消除掉多计算的边对结果产生的影响。
首先可以用dfs预处理出到根节点的异或和存储在数组中。树上两节点之间的路径唯一,所以随便选取一个根节点即可。
现在求解两个路径异或和的最大值即求两个节点a , b ;
r e s = m a x ( r e s , s u m [ a ] ⨁ s u m [ b ] ) res = max(res , sum[a]\bigoplus sum[b] ) res=max(res,sum[a]⨁sum[b])
用 Tire树+贪心 解决可以这个问题
根据二进制从高到低给每个 sum[i] 建立 Tire 树 , 枚举每个i按二进制求答案,优先考虑答案在当前位为1的情况(即存在 s o n [ p ] [ t m p ⨁ 1 ] son[p][tmp\bigoplus 1] son[p][tmp⨁1]) ,这样可以尽量使res最大。
最后全局的res最大值即为答案
Code
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 4e6 + 10 ;
typedef long long ll ;
int son[N][2], sum[N] , idx;
int h[N] , e[N] , ne[N] , cnt;
int w[N<<1] ;
void add(int a,int b,int c){
e[cnt] = b , ne[cnt] = h[a] , w[cnt] = c, h[a] = cnt++ ;
}
void dfs(int u , int fa , int x ){
sum[u] = x;
for(int i = h[u] ; ~i ; i = ne[i] ){
int v = e[i] ;
if( v == fa ) continue ;
dfs(v,u,x^w[i]) ;
}
}
void build(int n){
for(int i = 1 ; i <= n ; i ++ ) {
int x = sum[i] , p = 0 ;
for(int j = 30 ; j >= 0 ; j -- ){
int u = x >> j & 1 ;
if(!son[p][u]) son[p][u] = ++ idx;
p = son[p][u] ;
}
}
}
int query(int n){
int p = 0 , res = 0 ;
for(int i = 30 ; i >= 0 ; i -- ){
int tmp = n >> i & 1 ;
if(son[p][tmp^1]) {
res += 1 << i ;
p = son[p][tmp^1] ;
}
else p = son[p][tmp] ;
}
return res;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
memset(h,-1,sizeof(h)) ;
int n ;
scanf("%d",&n) ;
for(int i = 1 ; i < n ; i ++ ){
int a , b , c ;
cin >> a >> b >> c;
add(a,b,c) , add(b,a,c) ;
}
dfs(1,-1,0);
build(n);
int res =0 ;
for(int i = 1 ; i <= n ; i ++ ) res = max(res,query(sum[i])) ;
cout << res << endl;
return 0 ;
}