【P4551 最长异或路径】

P4551 最长异或路径

题目描述

给定一棵 nnn 个点的带权树,结点下标从 111 开始到 nnn。求树中所有异或路径的最大值。

异或路径指树上两个结点之间唯一路径上的所有边权的异或值。

输入格式

第一行一个整数 nnn,表示结点数。

接下来 n−1n-1n−1 行,给出 u,v,wu,v,wu,v,w ,分别表示树上的 uuu 点和 vvv 点有连边,边的权值是 www。

输出格式

一行,一个整数表示答案。

输入输出样例 #1

输入 #1

复制代码
4
1 2 3
2 3 4
2 4 6

输出 #1

复制代码
7

说明/提示

当两个结点分别是 1,31,31,3 时,答案是 7=3⊕47=3\oplus 47=3⊕4,取最大值。

数据范围

1≤n≤105;0<u,v≤n;0≤w<2311\le n \le 10^5;0 < u,v \le n;0 \le w < 2^{31}1≤n≤105;0<u,v≤n;0≤w<231。

前缀和

1.树,无环,所以任意节点都可以是根

2.现在求的是"树上两个结点之间唯一路径上的所有边权的异或值",如节点1^节点2^节点3^节点4,现在"异或路径的最大值"其实就是节点1到节点3间的异或值是7。

3.现在任意选一个点为根,这里方便期间选的是1,求前缀异或和,就是各节点到1的异或和,这个可以pre_xor[1]=0,

pre_xor[2]=pre_xor[1]^3,

pre_xor[3]=pre_xor[2]^4,

pre_xor[4]=pre_xor[4]^6。

这个过程可以用宽搜完成。O(n)

4.前缀和间的异或就是最大异或值,这里就是pre_xor[3]^pre_xor[0]=7。

5.把各节点到根节点的前缀异或和写成01字典树,共32层,从根往下是对应的二进制,每层都是其中一位二进制,能表示所有的int(高位自然是0)。那这些数间的最大异或值,只需找最大异或对,就是两前缀和的32层每层都尽量是相反数,而且尽量满足高位相反。O(32n)

6.O(n+32n),远优于暴力(n2)

代码

#include <bits/stdc++.h>

using namespace std;

struct node{//字典树节点

node *l,*r;//左右子节点

node():l(NULL),r(NULL){}//构造函数

};

class trie{//字典树类

private://私有变量和函数

int max_xor;//两个结点之间唯一路径上的所有边权的异或值。

node *root;//根节点

void destroy(node cur){//销毁占有内存
if(cur->l)destroy(cur->l);//先销毁子节点
if(cur->r)destroy(cur->r);
delete cur;//再删除该节点
}
public:
trie(){max_xor=0;root=new node();}//类构造函数
~trie(){destroy(root);}//析构函数
void insert_query(int x){//插入每个节点到根节点的前缀异或和
/

求所有节点间唯一路径上所有边权的异或值。

到根节点前缀异或和间的异或

到根节点的公共祖先部分会抵消------a^a=0,只剩节点间的路径部分的异或值

*/

int cur_xor=0;//初始异或

node *cur=root,*oth=root;//本前缀和在字典树中的位置和相反前缀和节点位置。a!=b

for(int i=31;i>=0;i--){//存够32层,能表示所有int

int bit=(x>>i&1);//取出x的第i位二进制数(i变小就是从高位开始)

if(bit){//1表示接右树

if(!cur->r)cur->r=new node();//没右节点就建立

cur=cur->r;//到下层

if(oth->l){//相反数就得左树0

cur_xor|=(1<<i);//结果的第i位要保证是1

oth=oth->l;//相反数找下一层

}else if(oth->r)oth=oth->r;//没左节点就只好右节点,原i位数二进制不变

else oth=NULL;

}else{//0表示接左树

if(!cur->l)cur->l=new node();

cur=cur->l;

if(oth->r){

cur_xor|=(1<<i);

oth=oth->r;

}else if(oth->l)oth=oth->l;

else oth=NULL;

}

}

max_xor=max(max_xor,cur_xor);//得到最大前缀异或和间异或值

}

int getans(){return max_xor;}

void view(){//层数遍历打印字典树

node cur=root;//当前节点初始是根节点
int cur_level=0,bit,level;//层数深度和当前深度
queue<pair<node
,pair<int,int>>> q;//宽搜队列,三元素:节点、左右和深度

q.push({cur,{-1,0}});//根节点放进队列

while(!q.empty()){//非空就宽搜

cur=q.front().first,bit=q.front().second.first,level=q.front().second.second;q.pop();//当前节点、左右类型、深度

if(cur_level<level){//换深度,输出换行

cout<<endl;cur_level=level;

}

if(bit==-1)cout<<"root\t";//输出根

else if(bit)cout<<"1\t";//右树

else cout<<"0\t";//左树

if(cur->r)q.push({cur->r,{1,level+1}});//递归右子树

if(cur->l)q.push({cur->l,{0,level+1}});//递归左子树

}

}

}t;

const int N=1e5+1;

int n,

u,v,w,//两节点间权值

pre_xor[N];//前缀和

vector<pair<int,int>> adj[N];//每个节点连接的节点和权值

queue q;//宽搜队列

int main(){

//freopen("data.cpp","r",stdin);

cin>>n;

for(int i=1;i<n;i++){//遍历n-1个边

cin>>u>>v>>w;

adj[u].push_back({v,w});//u出发的终点和权值

adj[v].push_back({u,w});//双向

}

q.push(1);pre_xor[1]=0;t.insert_query(pre_xor[1]);//根节点出发,把根节点放进字典树

while(!q.empty()){//宽搜遍历所有节点,计算到各节点的前缀异或和

u=q.front();q.pop();//出发点

for(int i=0;i<adj[u].size();i++){//遍历u点出发的所有终点

v=adj[u][i].first;

w=adj[u][i].second;

if(!pre_xor[v]&&v!=1){//如果没计算过,而且非根节点

pre_xor[v]=pre_xor[u]^w;q.push(v);//前缀异或和

t.insert_query(pre_xor[v]);//计算当前的最大的前缀异或和间的异或值

}

}

}

cout<<t.getans();

return 0;

}

小结

01字典树可以快速找到最大异或对。

相关推荐
CoovallyAIHub1 小时前
2025年值得关注的5款数据标注工具
深度学习·算法·计算机视觉
FuckPatience1 小时前
C# 补码
开发语言·算法·c#
稚辉君.MCA_P8_Java1 小时前
Gemini永久会员 VB返回最长有效子串长度
数据结构·后端·算法
小年糕是糕手1 小时前
【C++】类和对象(五) -- 类型转换、static成员
开发语言·c++·程序人生·考研·算法·visual studio·改行学it
Xの哲學1 小时前
Linux内核数据结构:设计哲学与实现机制
linux·服务器·算法·架构·边缘计算
秋深枫叶红1 小时前
嵌入式第二十七篇——数据结构——栈
c语言·数据结构·学习·算法
稚辉君.MCA_P8_Java1 小时前
Gemini永久会员 Java 返回最长有效子串长度
java·数据结构·后端·算法
Swift社区1 小时前
LeetCode 440 - 字典序的第 K 小数字
算法·leetcode·职场和发展
youngee111 小时前
hot100-42二叉树的右视图
算法